@tiledesk/tiledesk-voice-twilio-connector 0.1.26-rc11 → 0.1.26-rc13
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 +234 -190
- package/logs/app.log +92 -0
- package/package.json +1 -1
- package/tiledesk/TiledeskChannel.js +1 -1
- package/tiledesk/TiledeskTwilioTranslator.js +67 -33
- 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/index.js
CHANGED
|
@@ -98,12 +98,6 @@ router.post("/tiledesk", async (req, res) => {
|
|
|
98
98
|
});
|
|
99
99
|
tdChannel.setProjectId(project_id)
|
|
100
100
|
|
|
101
|
-
/*SKIP INFO MESSAGES*/
|
|
102
|
-
/*SKIP CURRENT USER MESSAGES*/
|
|
103
|
-
if(!utilsMess.messageType(TYPE_MESSAGE.INFO, tiledeskMessage) && !(tiledeskMessage.sender.indexOf("vxml") > -1) ){
|
|
104
|
-
winston.debug("> whook SAVE MESSAGE TO QUEUE " + JSON.stringify(tiledeskMessage) );
|
|
105
|
-
}
|
|
106
|
-
|
|
107
101
|
await tdChannel.addMessageToQueue(tiledeskMessage)
|
|
108
102
|
|
|
109
103
|
res.send("(voice) Message received from Voice Twilio Proxy");
|
|
@@ -145,7 +139,8 @@ router.post('/webhook/:id_project', async (req, res) => {
|
|
|
145
139
|
|
|
146
140
|
const tdTranslator = new TiledeskTwilioTranslator({
|
|
147
141
|
BASE_URL: BASE_URL,
|
|
148
|
-
aiService: aiService
|
|
142
|
+
aiService: aiService,
|
|
143
|
+
uploadService: uploadService
|
|
149
144
|
});
|
|
150
145
|
|
|
151
146
|
let start2 = new Date().getTime();
|
|
@@ -155,32 +150,33 @@ router.post('/webhook/:id_project', async (req, res) => {
|
|
|
155
150
|
return;
|
|
156
151
|
}
|
|
157
152
|
let end2 = new Date().getTime();
|
|
158
|
-
// console.log('Time after signIn: ', end2-start2, '[ms]')
|
|
159
153
|
|
|
160
154
|
//let conversation_id = await tdChannel.getConversation(ani, callId, user.token);
|
|
161
155
|
let conversation_id = await tdChannel.generateConversation(from, callSid, user.token);
|
|
162
156
|
winston.debug("(voice) conversation returned:"+ conversation_id);
|
|
163
157
|
|
|
164
|
-
|
|
165
|
-
//GET AND SAVE GPT-KET IF
|
|
166
158
|
let integrations = [], publicKey = false;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
159
|
+
try {
|
|
160
|
+
//GET AND SAVE GPT-KET IF
|
|
161
|
+
let key = await integrationService.getKeyFromIntegrations(project_id, 'openai', settings.token)
|
|
162
|
+
if (!key) {
|
|
163
|
+
winston.debug("(voice) - Key not found in Integrations. Searching in kb settings...");
|
|
164
|
+
key = await integrationService.getKeyFromKbSettings(project_id, settings.token);
|
|
165
|
+
}
|
|
166
|
+
if (!key) {
|
|
167
|
+
winston.debug("(voice) - Retrieve public gptkey")
|
|
168
|
+
key = GPT_KEY;
|
|
169
|
+
publicKey = true;
|
|
170
|
+
}
|
|
171
|
+
integrations.push({type: 'openai', key: key, publicKey: publicKey})
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
173
|
+
let eleven_labs = await integrationService.getKeyFromIntegrations(project_id, 'elevenlabs', settings.token)
|
|
174
|
+
if (eleven_labs) {
|
|
175
|
+
winston.debug("(voice) - Key found in Integrations: "+ eleven_labs);
|
|
176
|
+
integrations.push({type: 'elevenlabs', key: eleven_labs, publicKey: false})
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
winston.error('(voice) - Error retrieving integrations keys:', error);
|
|
184
180
|
}
|
|
185
181
|
|
|
186
182
|
//save data to redis
|
|
@@ -226,7 +222,7 @@ router.post('/webhook/:id_project', async (req, res) => {
|
|
|
226
222
|
let start_time_get_message = new Date()
|
|
227
223
|
let message = await getMessage(callSid, from, project_id, conversation_id)
|
|
228
224
|
let end_time_get_message = new Date()
|
|
229
|
-
winston.verbose(
|
|
225
|
+
winston.verbose(`Time to getMessage from queue in /webhook/:${project_id} : ${(end_time_get_message-start_time_get_message)}[ms] --- at time:` + new Date())
|
|
230
226
|
|
|
231
227
|
// //generate Tiledesk wait message
|
|
232
228
|
// let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
|
|
@@ -239,7 +235,7 @@ router.post('/webhook/:id_project', async (req, res) => {
|
|
|
239
235
|
winston.debug('(voice) /webhook/:id_project messageVXML-->'+ messageToVXML)
|
|
240
236
|
|
|
241
237
|
let end_call = new Date().getTime();
|
|
242
|
-
winston.info(`Time to respond to /webhook/${project_id}
|
|
238
|
+
winston.info(`Time to respond to /webhook/${project_id}: ${(end_call-start_call)}[ms]`)
|
|
243
239
|
|
|
244
240
|
// Render the response as XML in reply to the webhook request
|
|
245
241
|
res.set('Content-Type', 'text/xml');
|
|
@@ -251,7 +247,6 @@ router.post('/nextblock_old/:callSid/', async(req, res) => {
|
|
|
251
247
|
let start_call = new Date()
|
|
252
248
|
winston.debug("(voice) called POST /nextblock ", req.body);
|
|
253
249
|
winston.debug("(voice) called POST /nextblock query ", req.query);
|
|
254
|
-
console.log('/nextblock at: ', new Date(), 'with text:', req.body.SpeechResult)
|
|
255
250
|
|
|
256
251
|
let usertext = req.body.SpeechResult;
|
|
257
252
|
let confidence = req.body.Confidence
|
|
@@ -325,7 +320,7 @@ router.post('/nextblock_old/:callSid/', async(req, res) => {
|
|
|
325
320
|
|
|
326
321
|
// convert response to vxml
|
|
327
322
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
328
|
-
winston.
|
|
323
|
+
winston.debug("(voice) VXML to SEND: "+ messageToVXML);
|
|
329
324
|
|
|
330
325
|
let end_call = new Date()
|
|
331
326
|
console.log('Time to responde to /nextblock/:callSid : ', end_call-start_call, '[ms]')
|
|
@@ -338,8 +333,6 @@ router.post('/nextblock_old/:callSid/', async(req, res) => {
|
|
|
338
333
|
|
|
339
334
|
router.post('/nextblock/:callSid/', async(req, res) => {
|
|
340
335
|
let start_call = new Date()
|
|
341
|
-
winston.debug("(voice) called POST /nextblock ", req.body);
|
|
342
|
-
winston.debug("(voice) called POST /nextblock query ", req.query);
|
|
343
336
|
winston.verbose("(voice) called POST /nextblock at" + new Date() + "with text: "+ req.body.SpeechResult);
|
|
344
337
|
|
|
345
338
|
let usertext = req.body.SpeechResult;
|
|
@@ -404,7 +397,6 @@ router.post('/nextblock/:callSid/', async(req, res) => {
|
|
|
404
397
|
let start_time_send_message = new Date()
|
|
405
398
|
let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
|
|
406
399
|
let end_time_send_message = new Date()
|
|
407
|
-
winston.debug("message sent : ", tdMessage);
|
|
408
400
|
winston.verbose(`(else) Time to send message to tiledesk in /nextblock/${callSid} : ${(end_time_send_message-start_time_send_message)}[ms] with text ` + tdMessage.text + ' --- at time:' + new Date())
|
|
409
401
|
|
|
410
402
|
let start_time_get_message = new Date()
|
|
@@ -422,7 +414,7 @@ router.post('/nextblock/:callSid/', async(req, res) => {
|
|
|
422
414
|
}
|
|
423
415
|
})
|
|
424
416
|
let end_promise_message = new Date()
|
|
425
|
-
winston.verbose(`Time to manage message in Promise /nextblock/${callSid}: ${(end_promise_message-start_promise_message)}[ms]` + ' with text' + message.text + ' --- at time --' + new Date())
|
|
417
|
+
winston.verbose(`Time to manage message in Promise /nextblock/${callSid}: ${(end_promise_message-start_promise_message)}[ms]` + ' with text:' + message.text + ' --- at time --' + new Date())
|
|
426
418
|
|
|
427
419
|
// convert response to vxml
|
|
428
420
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
@@ -524,8 +516,6 @@ async function getMessage(callSid, ani, project_id, conversation_id){
|
|
|
524
516
|
|
|
525
517
|
router.post('/speechresult/:callSid', async (req, res) => {
|
|
526
518
|
let start_call = new Date();
|
|
527
|
-
winston.debug("(voice) called POST /speechresult ", req.body);
|
|
528
|
-
winston.debug("(voice) called POST /speechresult query ", req.query);
|
|
529
519
|
winston.verbose("(voice) called POST /speechresult at" + new Date() + "with text: "+ req.body.SpeechResult);
|
|
530
520
|
|
|
531
521
|
let usertext = req.body.SpeechResult;
|
|
@@ -611,7 +601,7 @@ router.post('/speechresult/:callSid', async (req, res) => {
|
|
|
611
601
|
|
|
612
602
|
// convert response to vxml
|
|
613
603
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
614
|
-
winston.
|
|
604
|
+
winston.debug("(voice) VXML to SEND: "+ messageToVXML);
|
|
615
605
|
|
|
616
606
|
let end_call = new Date()
|
|
617
607
|
winston.info(`Time to respond to /speechresult/${callSid} : ${(end_call-start_call)} [ms]`)
|
|
@@ -621,12 +611,142 @@ router.post('/speechresult/:callSid', async (req, res) => {
|
|
|
621
611
|
res.status(200).send(messageToVXML);
|
|
622
612
|
})
|
|
623
613
|
|
|
614
|
+
/* ----> called with Record tag in action property <----- */
|
|
615
|
+
router.post('/record/action/:callSid/',async (req, res) => {
|
|
616
|
+
winston.verbose('+++++++++++(voice) called POST record/action/:callSid at time ' + new Date());
|
|
617
|
+
|
|
618
|
+
let callSid = req.body.CallSid;
|
|
619
|
+
let sessionInfo;
|
|
620
|
+
let project_id, conversation_id, user;
|
|
621
|
+
let from, to;
|
|
622
|
+
|
|
623
|
+
let redis_data = await redis_client.get('tiledesk:voice:'+callSid+':session');
|
|
624
|
+
if (!redis_data) {
|
|
625
|
+
return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
|
|
626
|
+
}
|
|
627
|
+
sessionInfo = JSON.parse(redis_data)
|
|
628
|
+
project_id = sessionInfo.project_id;
|
|
629
|
+
from = sessionInfo.from;
|
|
630
|
+
to = sessionInfo.to;
|
|
631
|
+
conversation_id = sessionInfo.conversation_id;
|
|
632
|
+
user = sessionInfo.user;
|
|
633
|
+
|
|
634
|
+
let vxmlAttributes = {
|
|
635
|
+
TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
|
|
636
|
+
TTS_VOICE_NAME: VOICE_NAME,
|
|
637
|
+
callSid: callSid,
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const tdChannel = new TiledeskChannel({
|
|
641
|
+
API_URL: API_URL,
|
|
642
|
+
redis_client: redis_client
|
|
643
|
+
})
|
|
644
|
+
tdChannel.setProjectId(project_id)
|
|
645
|
+
|
|
646
|
+
const tdTranslator = new TiledeskTwilioTranslator({
|
|
647
|
+
BASE_URL: BASE_URL,
|
|
648
|
+
aiService: aiService,
|
|
649
|
+
uploadService: uploadService
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
let start_time_get_message = new Date()
|
|
654
|
+
let message = await getMessage(callSid, from, project_id, conversation_id)
|
|
655
|
+
winston.debug('message from getMessage in /record/action/: ', message)
|
|
656
|
+
let end_time_get_message = new Date()
|
|
657
|
+
winston.verbose(`Time to getMessage from queue in /record/action/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]` + ' --- at time:' + new Date())
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
//generate Tiledesk wait message
|
|
661
|
+
// let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
|
|
662
|
+
// let message = await tdChannel.generateWaitTdMessage(from, delayTime)
|
|
663
|
+
// //update delayIndex for wait command message time
|
|
664
|
+
// await voiceChannel.saveDelayIndexForCallId(callSid)
|
|
665
|
+
|
|
666
|
+
// convert response to vxml
|
|
667
|
+
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
668
|
+
winston.debug("(voice) /record/action VXML to SEND: "+ messageToVXML);
|
|
669
|
+
|
|
670
|
+
res.set('Content-Type', 'application/xml');
|
|
671
|
+
res.status(200).send(messageToVXML);
|
|
672
|
+
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
/* ----> called with Record tag in recordingStatusCallback property <----- */
|
|
676
|
+
router.post('/record/callback/:callSid/',async (req, res) => {
|
|
677
|
+
winston.verbose('+++++++++++(voice) called POST record/callback/:callSid at time', new Date());
|
|
678
|
+
let start_call = new Date();
|
|
679
|
+
|
|
680
|
+
let callSid = req.params.callSid || req.body.CallSid;
|
|
681
|
+
let audioFileUrl = req.body.RecordingUrl;
|
|
682
|
+
let audioFileDuration = req.body.RecordingDuration;
|
|
683
|
+
let button_action = req.query.button_action ? '#' + req.query.button_action : '';
|
|
684
|
+
let previousIntentName = req.query.intentName || '';
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
let sessionInfo;
|
|
688
|
+
let project_id, conversation_id, user;
|
|
689
|
+
let from, to;
|
|
690
|
+
|
|
691
|
+
let redis_data = await redis_client.get('tiledesk:voice:'+callSid+':session');
|
|
692
|
+
if (!redis_data) {
|
|
693
|
+
return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
|
|
694
|
+
}
|
|
695
|
+
sessionInfo = JSON.parse(redis_data)
|
|
696
|
+
project_id = sessionInfo.project_id;
|
|
697
|
+
from = sessionInfo.from;
|
|
698
|
+
to = sessionInfo.to;
|
|
699
|
+
conversation_id = sessionInfo.conversation_id;
|
|
700
|
+
user = sessionInfo.user;
|
|
701
|
+
|
|
702
|
+
const tdChannel = new TiledeskChannel({
|
|
703
|
+
API_URL: API_URL,
|
|
704
|
+
redis_client: redis_client
|
|
705
|
+
})
|
|
706
|
+
tdChannel.setProjectId(project_id)
|
|
707
|
+
|
|
708
|
+
const CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
|
|
709
|
+
let settings = await db.get(CONTENT_KEY);
|
|
710
|
+
if(!settings){
|
|
711
|
+
return res.status(404).send({error: "VOICE Channel not already connected"})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let tiledeskMessage = null;
|
|
715
|
+
// tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
|
|
716
|
+
|
|
717
|
+
//SPEECH TO TEXT
|
|
718
|
+
const attributes = await voiceChannel.getSettingsForCallId(callSid);
|
|
719
|
+
winston.debug(`[VOICE] getting text message from STT: ${audioFileUrl}, model: ${attributes.STT_MODEL}`);
|
|
720
|
+
// generateSTT ritorna sempre un oggetto coerente (anche vuoto o /close)
|
|
721
|
+
tiledeskMessage = await generateSTT(audioFileUrl, attributes, sessionInfo, settings)
|
|
722
|
+
winston.debug('[VOICE] tiledeskMessage from STT: ', tiledeskMessage)
|
|
723
|
+
if (!tiledeskMessage || Object.keys(tiledeskMessage).length === 0) {
|
|
724
|
+
winston.debug(`[VOICE] STT result empty, fallback to no_input branch for callSid ${callSid}`);
|
|
725
|
+
tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
|
|
726
|
+
}else {
|
|
727
|
+
const normalizedText = utils.normalizeSTT(tiledeskMessage.text);
|
|
728
|
+
winston.verbose(`[VOICE] normalized STT text: ${normalizedText} for callSid ${callSid}`);
|
|
729
|
+
if(!normalizedText){
|
|
730
|
+
tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
|
|
731
|
+
}else{
|
|
732
|
+
tiledeskMessage.text = normalizedText;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
//send message to tiledesk
|
|
737
|
+
let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
|
|
738
|
+
let end_call = new Date();
|
|
739
|
+
winston.info(`Time to respond to /record/callback/${callSid} : ${(end_call-start_call)} [ms]`)
|
|
740
|
+
|
|
741
|
+
res.status(200).send({ success: true , message: "Message sent to Tiledesk for callSid " + callSid});
|
|
742
|
+
})
|
|
743
|
+
|
|
624
744
|
|
|
625
745
|
router.post('/menublock/:callSid', async (req, res) => {
|
|
626
746
|
let start_call = new Date().getTime();
|
|
627
747
|
winston.debug("(voice) called POST /menu", req.body);
|
|
628
748
|
winston.debug("(voice) called POST /menu query" , req.query);
|
|
629
|
-
winston.verbose('/menublock at: '
|
|
749
|
+
winston.verbose('/menublock at: ' + new Date() + 'with text:'+ req.body.Digits)
|
|
630
750
|
|
|
631
751
|
let message_text = '';
|
|
632
752
|
let attributes = {};
|
|
@@ -730,7 +850,7 @@ router.post('/menublock/:callSid', async (req, res) => {
|
|
|
730
850
|
|
|
731
851
|
// convert response to vxml
|
|
732
852
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
733
|
-
winston.
|
|
853
|
+
winston.debug("(voice) VXML to SEND: "+ messageToVXML);
|
|
734
854
|
|
|
735
855
|
let end_call = new Date().getTime();
|
|
736
856
|
winston.info(`Time to respond to /menublock/${callSid} : ${(end_call-start_call)} [ms]`)
|
|
@@ -809,7 +929,7 @@ router.post('/handle/:callSid/:event', async (req, res) => {
|
|
|
809
929
|
|
|
810
930
|
// convert response to vxml
|
|
811
931
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes,sessionInfo)
|
|
812
|
-
winston.
|
|
932
|
+
winston.debug("(voice) VXML to SEND: "+ messageToVXML);
|
|
813
933
|
|
|
814
934
|
res.set('Content-Type', 'application/xml');
|
|
815
935
|
res.status(200).send(messageToVXML);
|
|
@@ -914,7 +1034,7 @@ router.post('/event/:callSid/:event', async(req, res)=> {
|
|
|
914
1034
|
|
|
915
1035
|
// convert response to vxml
|
|
916
1036
|
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
917
|
-
winston.
|
|
1037
|
+
winston.debug("(voice) VXML to SEND: "+ messageToVXML);
|
|
918
1038
|
|
|
919
1039
|
res.set('Content-Type', 'application/xml');
|
|
920
1040
|
res.status(200).send(messageToVXML);
|
|
@@ -989,8 +1109,7 @@ router.post('/twilio/status',async (req, res) => {
|
|
|
989
1109
|
let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
|
|
990
1110
|
|
|
991
1111
|
//remove session data for current callId and relative queue data
|
|
992
|
-
await
|
|
993
|
-
await redis_client.del('tiledesk:voice:'+callSid+':delayIndex');
|
|
1112
|
+
await voiceChannel.deleteCallKeys(callSid);
|
|
994
1113
|
await tdChannel.clearQueue(conversation_id);
|
|
995
1114
|
break;
|
|
996
1115
|
}
|
|
@@ -1011,172 +1130,97 @@ router.post('/twilio/fail',async (req, res) => {
|
|
|
1011
1130
|
})
|
|
1012
1131
|
|
|
1013
1132
|
|
|
1014
|
-
/* ----> catch Twilio Events <----- */
|
|
1015
|
-
router.post('/record/:callSid/',async (req, res) => {
|
|
1016
|
-
winston.debug('+++++++++++(voice) called POST record/:callSid ', req.body);
|
|
1017
|
-
|
|
1018
|
-
let callSid = req.body.CallSid;
|
|
1019
|
-
let audioFileUrl = req.body.RecordingUrl;
|
|
1020
|
-
|
|
1021
|
-
let sessionInfo;
|
|
1022
|
-
let project_id, conversation_id, user;
|
|
1023
|
-
let from, to;
|
|
1024
|
-
|
|
1025
|
-
let redis_data = await redis_client.get('tiledesk:voice:'+callSid+':session');
|
|
1026
|
-
if (!redis_data) {
|
|
1027
|
-
return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
|
|
1028
|
-
}
|
|
1029
|
-
sessionInfo = JSON.parse(redis_data)
|
|
1030
|
-
project_id = sessionInfo.project_id;
|
|
1031
|
-
from = sessionInfo.from;
|
|
1032
|
-
to = sessionInfo.to;
|
|
1033
|
-
conversation_id = sessionInfo.conversation_id;
|
|
1034
|
-
user = sessionInfo.user;
|
|
1035
|
-
|
|
1036
|
-
let vxmlAttributes = {
|
|
1037
|
-
TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
|
|
1038
|
-
TTS_VOICE_NAME: VOICE_NAME,
|
|
1039
|
-
callSid: callSid,
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const tdChannel = new TiledeskChannel({
|
|
1044
|
-
API_URL: API_URL,
|
|
1045
|
-
redis_client: redis_client
|
|
1046
|
-
})
|
|
1047
|
-
tdChannel.setProjectId(project_id)
|
|
1048
|
-
|
|
1049
|
-
const tdTranslator = new TiledeskTwilioTranslator({
|
|
1050
|
-
BASE_URL: BASE_URL,
|
|
1051
|
-
aiService: aiService,
|
|
1052
|
-
uploadService: uploadService
|
|
1053
|
-
});
|
|
1054
|
-
|
|
1055
|
-
const CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
|
|
1056
|
-
let settings = await db.get(CONTENT_KEY);
|
|
1057
|
-
if(!settings){
|
|
1058
|
-
return res.status(404).send({error: "VOICE Channel not already connected"})
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
let attributes = await voiceChannel.getSettingsForCallId(callSid);
|
|
1063
|
-
console.log('attributessss', attributes)
|
|
1064
|
-
|
|
1065
|
-
//SPEECH TO TEXT
|
|
1066
|
-
console.log('getting text message . . . ', audioFileUrl, attributes.STT_MODEL)
|
|
1067
|
-
let tiledeskMessage = await generateSTT(audioFileUrl, attributes, sessionInfo, settings)
|
|
1068
|
-
console.log('(voice) Message captured after STT -->', tiledeskMessage)
|
|
1069
|
-
|
|
1070
|
-
if(!tiledeskMessage){
|
|
1071
|
-
//case NO_INPUT
|
|
1072
|
-
const queryString = utils.buildQueryString(req.query);
|
|
1073
|
-
winston.debug('case no input.. redirect '+ queryString)
|
|
1074
|
-
|
|
1075
|
-
return await axios({
|
|
1076
|
-
url: "http://localhost:3000/handle/" + callSid + '/no_input'+ queryString,
|
|
1077
|
-
headers: req.headers,
|
|
1078
|
-
data: req.body,
|
|
1079
|
-
method: 'POST'
|
|
1080
|
-
}).then((response) => {
|
|
1081
|
-
winston.debug("[TiledeskChannel] speechToText response : ", response.data);
|
|
1082
|
-
return res.status(response.status).send(response.data);
|
|
1083
|
-
}).catch((err) => {
|
|
1084
|
-
winston.error("[TiledeskChannel] speechToText error: ", err);
|
|
1085
|
-
return res.status(500).send({ success: false, message: "Errore while redirect to /handle for callSid " + callSid});;
|
|
1086
|
-
})
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
|
|
1091
|
-
winston.debug("message sent : ", tdMessage);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
//generate Tiledesk wait message
|
|
1095
|
-
let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
|
|
1096
|
-
let message = await tdChannel.generateWaitTdMessage(from, delayTime)
|
|
1097
|
-
//update delayIndex for wait command message time
|
|
1098
|
-
await voiceChannel.saveDelayIndexForCallId(callSid)
|
|
1099
|
-
|
|
1100
|
-
// convert response to vxml
|
|
1101
|
-
let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
|
|
1102
|
-
winston.verbose("(voice) VXML to SEND: "+ messageToVXML);
|
|
1103
|
-
|
|
1104
|
-
res.set('Content-Type', 'application/xml');
|
|
1105
|
-
res.status(200).send(messageToVXML);
|
|
1106
|
-
|
|
1107
|
-
})
|
|
1108
1133
|
|
|
1109
1134
|
async function generateSTT(audioFileUrl, attributes, sessionInfo, settings){
|
|
1110
1135
|
|
|
1111
1136
|
winston.debug("(voice) generateSTT: "+ attributes.VOICE_PROVIDER);
|
|
1112
1137
|
|
|
1113
|
-
let tiledeskMessage = {}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
let
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1138
|
+
let tiledeskMessage = {};
|
|
1139
|
+
let text = null;
|
|
1140
|
+
|
|
1141
|
+
try {
|
|
1142
|
+
switch(attributes.VOICE_PROVIDER){
|
|
1143
|
+
case VOICE_PROVIDER.OPENAI: {
|
|
1144
|
+
let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
|
|
1145
|
+
let publicKey = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.publicKey
|
|
1146
|
+
if(publicKey){
|
|
1147
|
+
let keep_going = await aiService.checkQuoteAvailability(sessionInfo.project_id, settings.token)
|
|
1148
|
+
winston.debug('(voice) checkQuoteAvailability return: '+ keep_going);
|
|
1149
|
+
if(!keep_going){
|
|
1150
|
+
//no token is available --> close conversation
|
|
1151
|
+
return tiledeskMessage= {
|
|
1152
|
+
//text:'\\close',
|
|
1153
|
+
text:'/close',
|
|
1154
|
+
senderFullname: sessionInfo.from,
|
|
1155
|
+
type: 'text',
|
|
1156
|
+
channel: { name: CHANNEL_NAME },
|
|
1157
|
+
attributes: {
|
|
1158
|
+
subtype: "info",
|
|
1159
|
+
action: 'close'+JSON.stringify({event: 'quota_exceeded'}),
|
|
1160
|
+
payload: {
|
|
1161
|
+
catchEvent: 'quota_exceeded'
|
|
1162
|
+
},
|
|
1163
|
+
timestamp: 'xxxxxx'
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1140
1166
|
|
|
1167
|
+
}
|
|
1141
1168
|
}
|
|
1169
|
+
|
|
1170
|
+
text = await aiService.speechToText(audioFileUrl, attributes.STT_MODEL, GPT_KEY)
|
|
1171
|
+
break;
|
|
1142
1172
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
// La condizione negli input del metodo è corretta, ma può essere scritta in modo più leggibile:
|
|
1157
|
-
const ttsLanguage = attributes.TTS_LANGUAGE || 'en';
|
|
1158
|
-
text = await this.aiService.speechToTextElevenLabs(
|
|
1159
|
-
audioFileUrl,
|
|
1160
|
-
attributes.STT_MODEL,
|
|
1161
|
-
ttsLanguage,
|
|
1162
|
-
ELEVENLABS_APIKEY
|
|
1163
|
-
).catch((err) => {
|
|
1164
|
-
winston.error('errr while creating elevenlabs audio message', err?.response?.data);
|
|
1165
|
-
});
|
|
1166
|
-
tiledeskMessage= {
|
|
1173
|
+
case VOICE_PROVIDER.ELEVENLABS: {
|
|
1174
|
+
let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
|
|
1175
|
+
const ttsLanguage = attributes.TTS_LANGUAGE || 'en';
|
|
1176
|
+
text = await aiService.speechToTextElevenLabs( audioFileUrl, attributes.STT_MODEL, ttsLanguage, ELEVENLABS_APIKEY )
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
default:
|
|
1180
|
+
throw new Error('Unsupported VOICE_PROVIDER: ' + attributes.VOICE_PROVIDER);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if(text){
|
|
1184
|
+
winston.debug('[STT] text empty → fallback no_input');
|
|
1185
|
+
tiledeskMessage = {
|
|
1167
1186
|
text: text,
|
|
1168
1187
|
senderFullname: sessionInfo.from,
|
|
1169
1188
|
type: 'text',
|
|
1170
1189
|
channel: { name: CHANNEL_NAME }
|
|
1171
1190
|
};
|
|
1172
|
-
|
|
1191
|
+
}
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
winston.error('[STT] generateSTT error:', error);
|
|
1194
|
+
switch (error.code) {
|
|
1195
|
+
case 'AISERVICE_FAILED':
|
|
1196
|
+
winston.error('[STT] AISERVICE_FAILED → ', error.message);
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1173
1199
|
|
|
1200
|
+
// fallback: tiledeskMessage vuoto
|
|
1201
|
+
tiledeskMessage = {};
|
|
1202
|
+
|
|
1174
1203
|
}
|
|
1175
1204
|
|
|
1176
1205
|
return tiledeskMessage
|
|
1177
1206
|
}
|
|
1178
1207
|
|
|
1179
1208
|
|
|
1209
|
+
async function buildNoInputMessage(event, { from, button_action, payload }) {
|
|
1210
|
+
return {
|
|
1211
|
+
text: `/${event}`,
|
|
1212
|
+
senderFullname: from,
|
|
1213
|
+
type: 'text',
|
|
1214
|
+
channel: { name: CHANNEL_NAME },
|
|
1215
|
+
attributes: {
|
|
1216
|
+
type: 'info',
|
|
1217
|
+
action: button_action,
|
|
1218
|
+
payload: payload
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
|
|
1180
1224
|
|
|
1181
1225
|
router.get('/addon/transcript', async (req, res) => {
|
|
1182
1226
|
winston.debug("(voice) called GET /transcript query-->" , req.query);
|
|
@@ -1365,7 +1409,7 @@ async function connectRedis() {
|
|
|
1365
1409
|
|
|
1366
1410
|
|
|
1367
1411
|
redis_client.on('error', err => {
|
|
1368
|
-
winston.
|
|
1412
|
+
winston.error('(voice) Connect Redis Error ' + err);
|
|
1369
1413
|
})
|
|
1370
1414
|
/*
|
|
1371
1415
|
redis_client.on('connect', () => {
|