@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/index.js +328 -294
- package/logs/app.log +92 -0
- package/package.json +2 -1
- package/routes/manageApp.js +8 -8
- package/tiledesk/KVBaseMongo.js +1 -1
- package/tiledesk/TiledeskChannel.js +4 -4
- package/tiledesk/TiledeskTwilioTranslator.js +80 -45
- package/tiledesk/VoiceChannel.js +59 -35
- 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 +29 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiledesk/tiledesk-voice-twilio-connector",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
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();
|
|
@@ -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
|
|
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 =
|
|
127
|
+
const isInfoSupport = utils_message.messageType(TYPE_MESSAGE.INFO_SUPPORT, msg)
|
|
114
128
|
winston.debug("[TiledeskVXMLTranslator] isInfoSupport:"+ isInfoSupport);
|
|
115
|
-
if(isInfoSupport &&
|
|
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 + '/
|
|
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",
|
|
334
|
+
// gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) ).up();
|
|
317
335
|
//}
|
|
318
336
|
if(xmlAttributes && xmlAttributes.incompleteSpeechTimeout){
|
|
319
|
-
gather.att("speechTimeout",
|
|
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
|
-
|
|
360
|
+
.att("action", this.BASE_URL + '/record/action/' + xmlAttributes.callSid + queryUrl)
|
|
343
361
|
.att("method", "POST")
|
|
344
362
|
.att("trim", "trim-silence")
|
|
345
|
-
.att("
|
|
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
|
-
|
|
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 += '&
|
|
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 = '
|
|
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 = '
|
|
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(
|
|
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 },
|
|
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 },
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
|
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){
|
|
@@ -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(
|
|
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 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
|
|
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
|
}
|