@tiledesk/tiledesk-voice-twilio-connector 0.1.26-rc8 → 0.1.26
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/index.js +317 -253
- package/logs/app.log +92 -0
- package/package.json +1 -1
- package/routes/manageApp.js +8 -8
- package/tiledesk/KVBaseMongo.js +1 -1
- package/tiledesk/TiledeskChannel.js +4 -4
- package/tiledesk/TiledeskTwilioTranslator.js +71 -37
- package/tiledesk/VoiceChannel.js +42 -37
- package/tiledesk/constants.js +17 -1
- package/tiledesk/errors.js +28 -0
- package/tiledesk/services/AiService.js +91 -74
- package/tiledesk/services/UploadService.js +4 -1
- package/tiledesk/services/voiceEventEmitter.js +6 -0
- package/tiledesk/utils.js +20 -1
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
package/routes/manageApp.js
CHANGED
|
@@ -55,7 +55,7 @@ router.get("/", async (req, res) => {
|
|
|
55
55
|
|
|
56
56
|
router.get('/configure', async (req, res) => {
|
|
57
57
|
|
|
58
|
-
winston.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
355
|
+
winston.debug("(voice) Subscription: ", data)
|
|
356
356
|
resolve(data)
|
|
357
357
|
}).catch((error)=> {reject(error) })
|
|
358
358
|
|
package/tiledesk/KVBaseMongo.js
CHANGED
|
@@ -246,17 +246,17 @@ class TiledeskChannel {
|
|
|
246
246
|
|
|
247
247
|
/*SKIP INFO MESSAGES*/
|
|
248
248
|
if(utils.messageType(TYPE_MESSAGE.INFO, message)){
|
|
249
|
-
winston.
|
|
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.
|
|
255
|
+
winston.debug("> SKIPPING ECHO message: " + JSON.stringify(message) );
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
winston.
|
|
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();
|
|
@@ -11,11 +11,16 @@ const SETTING_MESSAGE = require('./constants').SETTING_MESSAGE
|
|
|
11
11
|
const CHANNEL_NAME = require('./constants').CHANNEL_NAME
|
|
12
12
|
const VOICE_PROVIDER = require('./constants').VOICE_PROVIDER;
|
|
13
13
|
const OPENAI_SETTINGS = require('./constants').OPENAI_SETTINGS;
|
|
14
|
+
const ELEVENLABS_SETTINGS = require('./constants').ELEVENLABS_SETTINGS;
|
|
14
15
|
|
|
15
16
|
const TYPE_ACTION_VXML = require('./constants').TYPE_ACTION_VXML
|
|
16
17
|
const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
|
|
17
18
|
const INFO_MESSAGE_TYPE = require('./constants').INFO_MESSAGE_TYPE
|
|
18
19
|
|
|
20
|
+
const voiceEventEmitter = require('./services/voiceEventEmitter');
|
|
21
|
+
|
|
22
|
+
const { SttError } = require('./errors');
|
|
23
|
+
|
|
19
24
|
class TiledeskTwilioTranslator {
|
|
20
25
|
/**
|
|
21
26
|
* Constructor for TiledeskVXMLTranslator
|
|
@@ -67,8 +72,8 @@ class TiledeskTwilioTranslator {
|
|
|
67
72
|
this.voiceProvider = VOICE_PROVIDER.TWILIO
|
|
68
73
|
if(flowAttributes.VOICE_PROVIDER){
|
|
69
74
|
this.voiceProvider = flowAttributes.VOICE_PROVIDER
|
|
70
|
-
|
|
71
75
|
}
|
|
76
|
+
vxmlAttributes.VOICE_PROVIDER = this.voiceProvider;
|
|
72
77
|
|
|
73
78
|
// IF VOICE_PROVIDER is TWILIO --> default values is on user account twilio settings
|
|
74
79
|
// IF VOICE_PROVIDER is OPENAI --> set default values from constants
|
|
@@ -77,13 +82,21 @@ class TiledeskTwilioTranslator {
|
|
|
77
82
|
vxmlAttributes.TTS_MODEL = flowAttributes.TTS_MODEL? flowAttributes.TTS_MODEL : OPENAI_SETTINGS.TTS_MODEL;
|
|
78
83
|
vxmlAttributes.STT_MODEL = flowAttributes.STT_MODEL? flowAttributes.STT_MODEL : OPENAI_SETTINGS.STT_MODEL;
|
|
79
84
|
}
|
|
80
|
-
|
|
85
|
+
|
|
86
|
+
// IF VOICE_PROVIDER is ELEVENLABS --> default values is on user account twilio settings
|
|
87
|
+
// IF VOICE_PROVIDER is ELEVENLABS --> set default values from constants
|
|
88
|
+
if(this.voiceProvider === VOICE_PROVIDER.ELEVENLABS){
|
|
89
|
+
vxmlAttributes.TTS_VOICE_NAME = flowAttributes.TTS_VOICE_NAME? flowAttributes.TTS_VOICE_NAME : ELEVENLABS_SETTINGS.TTS_VOICE_NAME;
|
|
90
|
+
vxmlAttributes.TTS_MODEL = flowAttributes.TTS_MODEL? flowAttributes.TTS_MODEL : ELEVENLABS_SETTINGS.TTS_MODEL;
|
|
91
|
+
vxmlAttributes.TTS_VOICE_LANGUAGE = flowAttributes.TTS_VOICE_LANGUAGE? flowAttributes.TTS_VOICE_LANGUAGE : ELEVENLABS_SETTINGS.TTS_VOICE_LANGUAGE;
|
|
92
|
+
vxmlAttributes.STT_MODEL = flowAttributes.STT_MODEL? flowAttributes.STT_MODEL : ELEVENLABS_SETTINGS.STT_MODEL;
|
|
93
|
+
}
|
|
81
94
|
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
97
|
winston.debug("[TiledeskVXMLTranslator] manageVoiceAttributes: vxmlAttributes returned:", vxmlAttributes);
|
|
98
|
+
voiceEventEmitter.emit('saveSettings', vxmlAttributes);
|
|
99
|
+
|
|
87
100
|
return vxmlAttributes
|
|
88
101
|
}
|
|
89
102
|
|
|
@@ -281,6 +294,9 @@ class TiledeskTwilioTranslator {
|
|
|
281
294
|
|
|
282
295
|
async delayVXMLConverter(rootEle, message, xmlAttributes){
|
|
283
296
|
const command = message.attributes.commands[0]
|
|
297
|
+
|
|
298
|
+
const prompt = this.promptVXML(rootEle, message, xmlAttributes);
|
|
299
|
+
|
|
284
300
|
rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid).up()
|
|
285
301
|
|
|
286
302
|
return rootEle.end({ pretty: true });
|
|
@@ -289,7 +305,7 @@ class TiledeskTwilioTranslator {
|
|
|
289
305
|
|
|
290
306
|
async playPromptVXMLConverter(rootEle, message, xmlAttributes){
|
|
291
307
|
|
|
292
|
-
const prompt = this.promptVXML(rootEle, message, xmlAttributes);
|
|
308
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
|
|
293
309
|
|
|
294
310
|
const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
|
|
295
311
|
rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
|
|
@@ -307,16 +323,17 @@ class TiledeskTwilioTranslator {
|
|
|
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 + '/
|
|
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")
|
|
314
331
|
|
|
315
332
|
//if(xmlAttributes && xmlAttributes.noInputTimeout){
|
|
316
|
-
// gather.att("timeout",
|
|
333
|
+
// gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) ).up();
|
|
317
334
|
//}
|
|
318
335
|
if(xmlAttributes && xmlAttributes.incompleteSpeechTimeout){
|
|
319
|
-
gather.att("speechTimeout",
|
|
336
|
+
gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
const prompt = this.promptVXML(gather, message, xmlAttributes);
|
|
@@ -339,15 +356,16 @@ class TiledeskTwilioTranslator {
|
|
|
339
356
|
}
|
|
340
357
|
|
|
341
358
|
record
|
|
342
|
-
|
|
359
|
+
.att("action", this.BASE_URL + '/record/action/' + xmlAttributes.callSid + queryUrl)
|
|
343
360
|
.att("method", "POST")
|
|
344
361
|
.att("trim", "trim-silence")
|
|
345
|
-
.att("
|
|
362
|
+
.att("timeout", "2")
|
|
363
|
+
.att("recordingStatusCallback", this.BASE_URL + '/record/callback/' + xmlAttributes.callSid + queryUrl)
|
|
346
364
|
.att("recordingStatusCallbackMethod", "POST")
|
|
347
365
|
|
|
348
|
-
if(xmlAttributes && xmlAttributes.noInputTimeout){
|
|
349
|
-
|
|
350
|
-
}
|
|
366
|
+
// if(xmlAttributes && xmlAttributes.noInputTimeout){
|
|
367
|
+
// record.att("timeout", xmlAttributes.noInputTimeout/1000 ).up();
|
|
368
|
+
// }
|
|
351
369
|
|
|
352
370
|
}
|
|
353
371
|
|
|
@@ -366,13 +384,13 @@ class TiledeskTwilioTranslator {
|
|
|
366
384
|
let queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now() + '&menu_options=' + menu_options;
|
|
367
385
|
const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
|
|
368
386
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoMatch){
|
|
369
|
-
queryUrl += '&
|
|
387
|
+
queryUrl += '&'+ handleNoInputNoMatchQuery.queryNoMatch
|
|
370
388
|
}
|
|
371
389
|
|
|
372
390
|
const gather = rootEle.ele("Gather", { input: "dtmf"})
|
|
373
391
|
|
|
374
392
|
|
|
375
|
-
gather.att("timeout", xmlAttributes.noInputTimeout/1000 )
|
|
393
|
+
gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) )
|
|
376
394
|
.att("numDigits", "1" )
|
|
377
395
|
.att("action", this.BASE_URL + '/menublock/' + xmlAttributes.callSid + queryUrl)
|
|
378
396
|
.att("method", "POST")
|
|
@@ -394,7 +412,7 @@ class TiledeskTwilioTranslator {
|
|
|
394
412
|
const gather = rootEle.ele("Gather", { input: "dtmf"})
|
|
395
413
|
|
|
396
414
|
const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + "&previousIntentTimestamp="+Date.now();
|
|
397
|
-
gather.att("timeout", xmlAttributes.noInputTimeout/1000 )
|
|
415
|
+
gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) )
|
|
398
416
|
.att("action", this.BASE_URL + '/menublock/' + xmlAttributes.callSid + queryUrl)
|
|
399
417
|
.att("method", "POST")
|
|
400
418
|
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
@@ -470,7 +488,7 @@ class TiledeskTwilioTranslator {
|
|
|
470
488
|
}
|
|
471
489
|
|
|
472
490
|
|
|
473
|
-
queryNoInput = '
|
|
491
|
+
queryNoInput = 'button_action='+button_noIput.action.substring(1);
|
|
474
492
|
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
|
|
475
493
|
|
|
476
494
|
|
|
@@ -497,7 +515,7 @@ class TiledeskTwilioTranslator {
|
|
|
497
515
|
value: 'no_match'
|
|
498
516
|
}
|
|
499
517
|
|
|
500
|
-
queryNoMatch = '
|
|
518
|
+
queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
|
|
501
519
|
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
|
|
502
520
|
|
|
503
521
|
/*element.ele("nomatch")
|
|
@@ -622,29 +640,45 @@ class TiledeskTwilioTranslator {
|
|
|
622
640
|
}
|
|
623
641
|
|
|
624
642
|
async generateTTS(text, attributes){
|
|
625
|
-
|
|
626
643
|
let audioData = null;
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
644
|
+
try {
|
|
645
|
+
switch(this.voiceProvider){
|
|
646
|
+
case VOICE_PROVIDER.OPENAI:
|
|
647
|
+
let GPT_KEY = this.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
|
|
648
|
+
audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
|
|
649
|
+
break;
|
|
650
|
+
case VOICE_PROVIDER.ELEVENLABS:
|
|
651
|
+
let ELEVENLABS_APIKEY = this.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
|
|
652
|
+
audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
|
|
653
|
+
break;
|
|
654
|
+
default:
|
|
655
|
+
throw new SttError('TTS_FAILED', 'Unsupported voice provider: ' + this.voiceProvider);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (!audioData) {
|
|
659
|
+
throw new SttError('TTS_FAILED', 'TTS returned no audio data');
|
|
660
|
+
}
|
|
640
661
|
|
|
662
|
+
let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, this.user)
|
|
663
|
+
winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
|
|
664
|
+
return fileUrl
|
|
665
|
+
} catch (error) {
|
|
666
|
+
winston.error('(voice) TTS generation error:', error);
|
|
667
|
+
switch (error.code) {
|
|
668
|
+
case 'TTS_FAILED':
|
|
669
|
+
winston.error('(voice) TTS_FAILED:', error.message);
|
|
670
|
+
break;
|
|
671
|
+
case 'AI_SERVICE_ERROR':
|
|
672
|
+
winston.error('(voice) AI_SERVICE_ERROR:', error.message);
|
|
673
|
+
break;
|
|
674
|
+
case 'UPLOAD_SERVICE_ERROR':
|
|
675
|
+
winston.error('(voice) UPLOAD_SERVICE_ERROR:', error.message);
|
|
676
|
+
break;
|
|
677
|
+
default:
|
|
678
|
+
throw new SttError('TTS_FAILED', 'TTS generation failed: ' + error.message);
|
|
679
|
+
}
|
|
641
680
|
}
|
|
642
681
|
|
|
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
682
|
}
|
|
649
683
|
|
|
650
684
|
|
package/tiledesk/VoiceChannel.js
CHANGED
|
@@ -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){
|
|
@@ -69,11 +75,11 @@ class VoiceChannel {
|
|
|
69
75
|
//increment
|
|
70
76
|
const delayIndex = (+index) +1
|
|
71
77
|
//save new index to redis
|
|
72
|
-
await this.redis_client.set('tiledesk:voice:'+callId + ':delayIndex', delayIndex,
|
|
78
|
+
await this.redis_client.set('tiledesk:voice:'+callId + ':delayIndex', delayIndex, 'EX', 86400);
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
//if index is not present: set to default (0)
|
|
76
|
-
await this.redis_client.set('tiledesk:voice:'+callId + ':delayIndex', 0,
|
|
82
|
+
await this.redis_client.set('tiledesk:voice:'+callId + ':delayIndex', 0, 'EX', 86400);
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/** RESET INDEX INTO REDIS DATA FOR CURRENT CALLID **/
|
|
@@ -94,50 +100,49 @@ 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(
|
|
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(
|
|
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 deleteCallKeys(callSid) {
|
|
129
|
+
const pattern = `tiledesk:voice:${callSid}:*`;
|
|
130
|
+
let cursor = 0;
|
|
131
|
+
|
|
132
|
+
do {
|
|
133
|
+
const reply = await this.redis_client.scan(cursor, {
|
|
134
|
+
MATCH: pattern,
|
|
135
|
+
COUNT: 100
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
cursor = reply.cursor;
|
|
139
|
+
const keys = reply.keys;
|
|
140
|
+
|
|
141
|
+
if (keys.length > 0) {
|
|
142
|
+
await this.redis_client.del(keys);
|
|
143
|
+
}
|
|
144
|
+
} while (cursor !== 0);
|
|
145
|
+
}
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
|
package/tiledesk/constants.js
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class SttError extends Error {
|
|
2
|
+
constructor(code, message, extra = {}) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.code = code; // es. 'AUDIO_DOWNLOAD_FAILED'
|
|
5
|
+
this.extra = extra; // opzionale, eventuali dati aggiuntivi
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class RedisError extends Error {
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ServiceError extends Error {
|
|
17
|
+
constructor(code, message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// esporta tutte insieme
|
|
24
|
+
module.exports = {
|
|
25
|
+
SttError,
|
|
26
|
+
RedisError,
|
|
27
|
+
ServiceError
|
|
28
|
+
};
|