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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +44 -0
  3. package/index.js +7 -1529
  4. package/package.json +23 -22
  5. package/src/app.js +146 -0
  6. package/src/config/index.js +32 -0
  7. package/src/controllers/VoiceController.js +488 -0
  8. package/src/controllers/VoiceController.original.js +811 -0
  9. package/src/middlewares/httpLogger.js +31 -0
  10. package/src/models/KeyValueStore.js +78 -0
  11. package/src/routes/manageApp.js +298 -0
  12. package/src/routes/voice.js +22 -0
  13. package/src/services/AiService.js +219 -0
  14. package/src/services/AiService.sdk.js +367 -0
  15. package/src/services/IntegrationService.js +74 -0
  16. package/src/services/MessageService.js +133 -0
  17. package/src/services/README_SDK.md +107 -0
  18. package/src/services/SessionService.js +143 -0
  19. package/src/services/SpeechService.js +134 -0
  20. package/src/services/TiledeskMessageBuilder.js +135 -0
  21. package/src/services/TwilioService.js +122 -0
  22. package/src/services/UploadService.js +78 -0
  23. package/src/services/channels/TiledeskChannel.js +269 -0
  24. package/{tiledesk → src/services/channels}/VoiceChannel.js +17 -56
  25. package/src/services/clients/TiledeskSubscriptionClient.js +78 -0
  26. package/src/services/index.js +45 -0
  27. package/src/services/translators/TiledeskTwilioTranslator.js +509 -0
  28. package/{tiledesk/TiledeskTwilioTranslator.js → src/services/translators/TiledeskTwilioTranslator.original.js} +119 -202
  29. package/src/utils/fileUtils.js +24 -0
  30. package/src/utils/logger.js +32 -0
  31. package/{tiledesk → src/utils}/utils-message.js +6 -21
  32. package/logs/app.log +0 -3082
  33. package/routes/manageApp.js +0 -419
  34. package/tiledesk/KVBaseMongo.js +0 -101
  35. package/tiledesk/TiledeskChannel.js +0 -363
  36. package/tiledesk/TiledeskSubscriptionClient.js +0 -135
  37. package/tiledesk/fileUtils.js +0 -55
  38. package/tiledesk/services/AiService.js +0 -230
  39. package/tiledesk/services/IntegrationService.js +0 -81
  40. package/tiledesk/services/UploadService.js +0 -88
  41. /package/{winston.js → src/config/logger.js} +0 -0
  42. /package/{tiledesk → src}/services/voiceEventEmitter.js +0 -0
  43. /package/{template → src/template}/configure.html +0 -0
  44. /package/{template → src/template}/css/configure.css +0 -0
  45. /package/{template → src/template}/css/error.css +0 -0
  46. /package/{template → src/template}/css/style.css +0 -0
  47. /package/{template → src/template}/error.html +0 -0
  48. /package/{tiledesk → src/utils}/constants.js +0 -0
  49. /package/{tiledesk → src/utils}/errors.js +0 -0
  50. /package/{tiledesk → src/utils}/utils.js +0 -0
package/index.js CHANGED
@@ -1,1531 +1,9 @@
1
- "use strict";
2
- const express = require("express");
3
- const bodyParser = require("body-parser")
4
- const router = express.Router();
5
- const winston = require("./winston");
6
- const fs = require("fs");
7
- const path = require('path');
8
- const pjson = require('./package.json');
9
- const axios = require("axios").default;
1
+ const { startServer, app } = require('./src/app');
10
2
 
3
+ // Export for use as npm package
4
+ module.exports = { startServer, app };
11
5
 
12
- router.use(bodyParser.json()); // support json encoded bodies
13
- router.use(express.urlencoded({ extended: true })); // support encoded bodies
14
- router.use(express.static(path.join(__dirname, 'template')));
15
-
16
- //template
17
- const manageApp = require('./routes/manageApp');
18
- const manageRoute = manageApp.router;
19
- router.use('/manage', manageRoute);
20
-
21
- // tiledesk clients
22
- const { TiledeskChannel } = require("./tiledesk/TiledeskChannel");
23
- const { TiledeskTwilioTranslator } = require('./tiledesk/TiledeskTwilioTranslator');
24
-
25
- //services
26
- const { IntegrationService } = require('./tiledesk/services/IntegrationService');
27
- const { AiService } = require('./tiledesk/services/AiService')
28
- const { UploadService } = require('./tiledesk/services/UploadService')
29
- let integrationService = null;
30
- let aiService = null;
31
- let uploadService = null;
32
-
33
- //voice clients
34
- const { VoiceChannel } = require("./tiledesk/VoiceChannel");
35
- let voiceChannel = null;
36
-
37
- // mongo
38
- const { KVBaseMongo } = require('./tiledesk/KVBaseMongo');
39
- const kvbase_collection = 'kvstore';
40
- const db = new KVBaseMongo(kvbase_collection);
41
-
42
- // redis
43
- var redis = require('redis')
44
- var redis_client;
45
-
46
- //UTILS
47
- const CHANNEL_NAME = require('./tiledesk/constants').CHANNEL_NAME
48
- const CALL_STATUS = require('./tiledesk/constants').CALL_STATUS
49
- const VOICE_NAME = require('./tiledesk/constants').VOICE_NAME
50
- const VOICE_LANGUAGE = require('./tiledesk/constants').VOICE_LANGUAGE
51
- const OPENAI_SETTINGS = require('./tiledesk/constants').OPENAI_SETTINGS
52
- const VOICE_PROVIDER = require('./tiledesk/constants').VOICE_PROVIDER
53
- let BASE_POOLING_DELAY = require('./tiledesk/constants').BASE_POOLING_DELAY
54
- let MAX_POLLING_TIME = require('./tiledesk/constants').MAX_POLLING_TIME
55
- const utils = require('./tiledesk/utils.js')
56
-
57
- let API_URL = null;
58
- let BASE_URL = null;
59
- let BASE_FILE_URL = null;
60
- let OPENAI_ENDPOINT = null;
61
- let ELEVENLABS_ENDPOINT = null;
62
- let REDIS_HOST = null;
63
- let REDIS_PORT = null;
64
- let REDIS_PASSWORD = null;
65
- let BRAND_NAME = null;
66
- let GPT_KEY = null;
67
-
68
- let twilio = require('twilio');
69
- const VoiceResponse = require('twilio').twiml.VoiceResponse;
70
- const { MessagingResponse } = require('twilio').twiml;
71
-
72
- let start1= ''
73
- let time = null;
74
-
75
- /*UTILS*/
76
- const utilsMessage = require('./tiledesk/utils-message.js')
77
- const TYPE_MESSAGE = require('./tiledesk/constants').TYPE_MESSAGE
78
-
79
- let messageTimeout = null;
80
-
81
- router.get("/", async (req, res) => {
82
- res.send("Welcome to Tiledesk-voice-twilio connector");
83
- });
84
-
85
-
86
- // TILEDESK WEBHOOK: message from tiledesk to user (chatbot/agent messagges)
87
- router.post("/tiledesk", async (req, res) => {
88
- winston.debug("(voice) Message received from Tiledesk in projectID: "+ req.body.payload.id_project + ' ---- and text: '+req.body.payload.text )
89
- let tiledeskMessage = req.body.payload
90
- let project_id = tiledeskMessage.id_project
91
-
92
- //const CONTENT_KEY = "voice-" + project_id;
93
- //let settings = await db.get(CONTENT_KEY)
94
-
95
- const tdChannel = new TiledeskChannel({
96
- API_URL: API_URL,
97
- redis_client: redis_client
98
- });
99
- tdChannel.setProjectId(project_id)
100
-
101
- /*SKIP INFO MESSAGES*/
102
- /*SKIP CURRENT USER MESSAGES*/
103
- if(!utilsMessage.messageType(TYPE_MESSAGE.INFO, tiledeskMessage) && !(tiledeskMessage.sender.indexOf("vxml") > -1) ){
104
- winston.verbose(`> whook SAVE MESSAGE "${tiledeskMessage.text}" TO QUEUE at time ` + new Date() );
105
- }
106
-
107
- await tdChannel.addMessageToQueue(tiledeskMessage)
108
-
109
- res.send("(voice) Message received from Voice Twilio Proxy");
110
- });
111
-
112
- // TWILIO WEBHOOK : message from user to tiledesk
113
- router.post('/webhook/:id_project', async (req, res) => {
114
- let start_call = new Date().getTime();
115
- winston.debug('(voice) called POST /webhook/:id_project '+ new Date(), req.params)
116
-
117
- let project_id = req.params.id_project;
118
- let callSid = req.body.CallSid;
119
- let from = req.body.From;
120
- let to = req.body.To;
121
-
122
- if(!from || !to){
123
- return res.status(404).send({error: "Error: Missing from/to parameters"})
124
- }
125
- from = utils.getNumber(from); //remove '+' from number
126
- to = utils.getNumber(to); //remove '+' from number
127
-
128
- const CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
129
- let settings = await db.get(CONTENT_KEY);
130
- if(!settings){
131
- return res.status(404).send({error: "VOICE Channel not already connected"})
132
- }
133
-
134
- let vxmlAttributes = {
135
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
136
- TTS_VOICE_NAME: VOICE_NAME,
137
- callSid: callSid
138
- };
139
-
140
- const tdChannel = new TiledeskChannel({
141
- API_URL: API_URL,
142
- redis_client: redis_client,
143
- });
144
- tdChannel.setProjectId(project_id)
145
-
146
- const tdTranslator = new TiledeskTwilioTranslator({
147
- BASE_URL: BASE_URL,
148
- aiService: aiService,
149
- uploadService: uploadService
150
- });
151
-
152
- let start2 = new Date().getTime();
153
- let user = await tdChannel.signIn(from, settings);
154
- if(!user){
155
- res.status(401).send({message: "Cannot able to signIn with current caller phone :" + from});
156
- return;
157
- }
158
- let end2 = new Date().getTime();
159
-
160
- //let conversation_id = await tdChannel.getConversation(ani, callId, user.token);
161
- let conversation_id = await tdChannel.generateConversation(from, callSid, user.token);
162
- winston.debug("(voice) conversation returned:"+ conversation_id);
163
-
164
- let integrations = [], publicKey = false;
165
- try {
166
- //GET AND SAVE GPT-KET IF
167
- let key = await integrationService.getKeyFromIntegrations(project_id, 'openai', settings.token)
168
- if (!key) {
169
- winston.debug("(voice) - Key not found in Integrations. Searching in kb settings...");
170
- key = await integrationService.getKeyFromKbSettings(project_id, settings.token);
171
- }
172
- if (!key) {
173
- winston.debug("(voice) - Retrieve public gptkey")
174
- key = GPT_KEY;
175
- publicKey = true;
176
- }
177
- integrations.push({type: 'openai', key: key, publicKey: publicKey})
178
-
179
- let eleven_labs = await integrationService.getKeyFromIntegrations(project_id, 'elevenlabs', settings.token)
180
- if (eleven_labs) {
181
- winston.debug("(voice) - Key found in Integrations: "+ eleven_labs);
182
- integrations.push({type: 'elevenlabs', key: eleven_labs, publicKey: false})
183
- }
184
- } catch (error) {
185
- winston.error('(voice) - Error retrieving integrations keys:', error);
186
- }
187
-
188
- //save data to redis
189
- let session_data = {
190
- from: from,
191
- to: to,
192
- callSid: callSid,
193
- project_id: project_id,
194
- user: user,
195
- conversation_id: conversation_id,
196
- integrations: integrations
197
- }
198
- voiceChannel.setSessionForCallId(callSid, session_data)
199
-
200
- let tiledeskMessage= {
201
- text:'/start',
202
- senderFullname: from,
203
- type: 'text',
204
- attributes: {
205
- subtype: 'info',
206
- payload: {
207
- ... req.body //send all attributes back to chatbot
208
- }
209
- },
210
- channel: { name: CHANNEL_NAME },
211
- departmentid: settings.department_id
212
- };
213
-
214
- let response = await tdChannel.send(tiledeskMessage, user.token, conversation_id)
215
- if(!response){
216
- return res.status(503).send({ message: "Bad response: Quota exceeded" })
217
- }
218
-
219
- //await for a response message from tiledesk queue
220
- let start_time_get_message = new Date()
221
- let message = await getMessage(callSid, from, project_id, conversation_id)
222
- let end_time_get_message = new Date()
223
- winston.verbose(`Time to getMessage from queue in /webhook/:${project_id} : ${(end_time_get_message-start_time_get_message)}[ms] --- at time:` + new Date())
224
-
225
- // //generate Tiledesk wait message
226
- // let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
227
- // let waitTiledeskMessage = await tdChannel.generateWaitTdMessage(from, delayTime)
228
- // //update delayIndex for wait command message time
229
- // await voiceChannel.saveDelayIndexForCallId(from)
230
-
231
- // send standard wait vxml message
232
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, session_data)
233
- winston.debug('(voice) /webhook/:id_project messageVXML-->'+ messageToVXML)
234
-
235
- let end_call = new Date().getTime();
236
- winston.info(`Time to respond to /webhook/${project_id}: ${(end_call-start_call)}[ms]`)
237
-
238
- // Render the response as XML in reply to the webhook request
239
- res.set('Content-Type', 'text/xml');
240
- res.status(200).send(messageToVXML);
241
- });
242
-
243
-
244
- router.post('/nextblock_old/:callSid/', async(req, res) => {
245
- let start_call = new Date()
246
- winston.debug("(voice) called POST /nextblock ", req.body);
247
- winston.debug("(voice) called POST /nextblock query ", req.query);
248
-
249
- let usertext = req.body.SpeechResult;
250
- let confidence = req.body.Confidence
251
- let callSid = req.params.callSid;
252
-
253
- let sessionInfo;
254
- let project_id, conversation_id, user;
255
- let from, to;
256
-
257
- let redis_data = await voiceChannel.getSessionForCallId(callSid)
258
- if (!redis_data) {
259
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
260
- }
261
- sessionInfo = JSON.parse(redis_data)
262
- project_id = sessionInfo.project_id;
263
- from = sessionInfo.from;
264
- to = sessionInfo.to;
265
- conversation_id = sessionInfo.conversation_id;
266
- user = sessionInfo.user;
267
-
268
- let vxmlAttributes = {
269
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
270
- TTS_VOICE_NAME: VOICE_NAME,
271
- callSid: callSid,
272
- };
273
-
274
-
275
- const tdChannel = new TiledeskChannel({
276
- API_URL: API_URL,
277
- redis_client: redis_client
278
- })
279
- tdChannel.setProjectId(project_id);
280
-
281
- const tdTranslator = new TiledeskTwilioTranslator({
282
- BASE_URL: BASE_URL,
283
- aiService: aiService,
284
- uploadService: uploadService
285
- });
286
-
287
-
288
-
289
- let message = await new Promise(async (resolve, reject) => {
290
- winston.debug('******* user text -->'+ usertext)
291
- if(usertext === '' || !usertext){
292
-
293
- let message = await getMessage(callSid, from, project_id, conversation_id)
294
-
295
- resolve(message)
296
- }else{
297
- //CASE:usertext is not empty and queue is empty --> send message to tiledesk and manage delayTime
298
- let tiledeskMessage= {
299
- text:usertext,
300
- senderFullname: from,
301
- type: 'text',
302
- channel: { name: CHANNEL_NAME }
303
- };
304
-
305
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
306
- winston.debug("message sent : ", tdMessage);
307
- //generate Tiledesk wait message
308
- let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
309
- let message = await tdChannel.generateWaitTdMessage(from, delayTime)
310
- //update delayIndex for wait command message time
311
- await voiceChannel.saveDelayIndexForCallId(callSid)
312
-
313
- resolve(message)
314
- }
315
- })
316
-
317
-
318
- // convert response to vxml
319
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
320
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
321
-
322
- let end_call = new Date()
323
- console.log('Time to responde to /nextblock/:callSid : ', end_call-start_call, '[ms]')
324
-
325
- // Render the response as XML in reply to the webhook request
326
- res.set('Content-Type', 'application/xml');
327
- res.status(200).send(messageToVXML);
328
-
329
- })
330
-
331
- router.post('/nextblock/:callSid/', async(req, res) => {
332
- let start_call = new Date()
333
- winston.verbose("(voice) called POST /nextblock at " + new Date() + "with text: "+ req.body.SpeechResult);
334
-
335
- let usertext = req.body.SpeechResult;
336
- let confidence = req.body.Confidence
337
- let callSid = req.params.callSid;
338
-
339
- let project_id, conversation_id, user;
340
- let from, to;
341
-
342
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
343
- if (!sessionInfo) {
344
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
345
- }
346
- project_id = sessionInfo.project_id;
347
- from = sessionInfo.from;
348
- to = sessionInfo.to;
349
- conversation_id = sessionInfo.conversation_id;
350
- user = sessionInfo.user;
351
-
352
- let vxmlAttributes = {
353
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
354
- TTS_VOICE_NAME: VOICE_NAME,
355
- callSid: callSid,
356
- };
357
-
358
-
359
- const tdChannel = new TiledeskChannel({
360
- API_URL: API_URL,
361
- redis_client: redis_client
362
- })
363
- tdChannel.setProjectId(project_id);
364
-
365
- const tdTranslator = new TiledeskTwilioTranslator({
366
- BASE_URL: BASE_URL,
367
- aiService: aiService,
368
- uploadService: uploadService
369
- });
370
-
371
-
372
- let start_promise_message= new Date();
373
- let message = await new Promise(async (resolve, reject) => {
374
- winston.debug('(voice) ******* user text -->'+ usertext)
375
- if(usertext === '' || !usertext){
376
-
377
- let start_time_get_message = new Date()
378
- let message = await getMessage(callSid, from, project_id, conversation_id)
379
- let end_time_get_message = new Date()
380
- winston.verbose(`(if) Time to getMessage from queue in /nextblock/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]`)
381
- resolve(message)
382
- }else{
383
- //CASE:usertext is not empty and queue is empty --> send message to tiledesk and manage delayTime
384
- let tiledeskMessage= {
385
- text:usertext,
386
- senderFullname: from,
387
- type: 'text',
388
- channel: { name: CHANNEL_NAME }
389
- };
390
-
391
- let start_time_send_message = new Date()
392
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
393
- let end_time_send_message = new Date()
394
- 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())
395
-
396
- let start_time_get_message = new Date()
397
- let message = await getMessage(callSid, from, project_id, conversation_id)
398
- let end_time_get_message = new Date()
399
- winston.verbose(`(else) Time to getMessage from queue in /nextblock/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]` + ' --- at time:' + new Date())
400
- resolve(message)
401
-
402
- //generate Tiledesk wait message
403
- // let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
404
- // let message = await tdChannel.generateWaitTdMessage(from, delayTime)
405
- // //update delayIndex for wait command message time
406
- // await voiceChannel.saveDelayIndexForCallId(callSid)
407
- // resolve(message)
408
- }
409
- })
410
- let end_promise_message = new Date()
411
- 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())
412
-
413
- // convert response to vxml
414
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
415
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
416
-
417
- let end_call = new Date()
418
- winston.info(`Time to respond to /nextblock/${callSid} : ${(end_call-start_call)} [ms]`)
419
-
420
- // Render the response as XML in reply to the webhook request
421
- res.set('Content-Type', 'application/xml');
422
- res.status(200).send(messageToVXML);
423
-
424
- })
425
-
426
- async function getMessage(callSid, ani, project_id, conversation_id){
427
- const startTime = Date.now();
428
-
429
- const tdChannel = new TiledeskChannel({
430
- API_URL: API_URL,
431
- redis_client: redis_client
432
- })
433
- tdChannel.setProjectId(project_id);
434
-
435
- let message = {}, queue = []
436
-
437
- //get queue
438
- //if queue exist and has at least one message
439
- //get message from queue, remove it from queue and clear delayTime
440
- //else
441
- //subscribe to topic
442
- //create promise that resolve after MAX_POLLING_TIME s
443
- //race between subscription and timeout and return a message
444
- //if subscription resolve first and queue has at least one message
445
- //get message from queue, remove it from queue and clear delayTime
446
-
447
- try{
448
-
449
- // 1. First attempt: read from queue
450
- queue = await tdChannel.getMessagesFromQueue(conversation_id)
451
- winston.debug('[getMessage] /NEXT check queue length--> '+ queue.length)
452
-
453
- if (queue && queue.length > 0) {
454
- //CASE: queue has at least one message to reproduce --> get message from tiledesk queue and reset delayTime
455
- message = queue[0]
456
- winston.verbose('[getMessage] QUEUE --> '+ queue[0].text)
457
-
458
- // remove message from queue and reset delayIndex
459
- await tdChannel.removeMessageFromQueue(conversation_id, message._id)
460
- await voiceChannel.clearDelayTimeForCallId(callSid)
461
-
462
- return message;
463
- }
464
-
465
- // 2. If queue is empty: subscribe with timeout
466
- if(queue && queue.length === 0){
467
- winston.debug("[getMessage] Queue is empty, starting subscription...");
468
- const subscriptionPromise = (async () => {
469
- await tdChannel.subscribeToTopic(conversation_id);
470
- queue = await tdChannel.getMessagesFromQueue(conversation_id)
471
-
472
- if (!queue || queue.length === 0) {
473
- throw new Error("No message received after subscription");
474
- }
475
-
476
- message = queue[0]
477
- winston.verbose(`[getMessage] Message received from subscription: ${message.text}`);
478
-
479
- // remove message from queue and reset delayIndex
480
- await tdChannel.removeMessageFromQueue(conversation_id, message._id)
481
- await voiceChannel.clearDelayTimeForCallId(callSid)
482
-
483
- return message;
484
- })();
485
-
486
- const timeoutPromise = new Promise(async (resolve) => {
487
- setTimeout(async () => {
488
- winston.debug("[getMessage] Subscription timeout, generating waitTdMessage...");
489
-
490
- //CASE: queue is empty --> generate Tiledesk wait message and manage delayTime
491
- const delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid);
492
- const waitMessage = await tdChannel.generateWaitTdMessage(ani, delayTime);
493
- //update delayIndex for wait command message time
494
- await voiceChannel.saveDelayIndexForCallId(callSid);
495
-
496
- resolve(waitMessage);
497
- }, MAX_POLLING_TIME * 1000); // MAX_POLLING_TIME is in seconds
498
- });
499
-
500
- // Vince chi arriva prima: subscription o timeout
501
- return await Promise.race([subscriptionPromise, timeoutPromise]);
502
- }
503
- }catch(err){
504
- winston.debug("[getMessage] Error occurred: ", err);
505
- reject(err);
506
- return;
507
- }
508
-
509
- }
510
-
511
- router.post('/speechresult/:callSid', async (req, res) => {
512
- let start_call = new Date();
513
- winston.verbose("(voice) called POST /speechresult at" + new Date() + "with text: "+ req.body.SpeechResult);
514
-
515
- let usertext = req.body.SpeechResult;
516
- let confidence = req.body.Confidence
517
- let callSid = req.params.callSid;
518
-
519
- let project_id, conversation_id, user;
520
- let from, to;
521
-
522
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
523
- if (!sessionInfo) {
524
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
525
- }
526
- project_id = sessionInfo.project_id;
527
- from = sessionInfo.from;
528
- to = sessionInfo.to;
529
- conversation_id = sessionInfo.conversation_id;
530
- user = sessionInfo.user;
531
-
532
- let vxmlAttributes = {
533
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
534
- TTS_VOICE_NAME: VOICE_NAME,
535
- callSid: callSid,
536
- };
537
-
538
- const tdChannel = new TiledeskChannel({
539
- API_URL: API_URL,
540
- redis_client: redis_client
541
- })
542
- tdChannel.setProjectId(project_id);
543
-
544
- const tdTranslator = new TiledeskTwilioTranslator({
545
- BASE_URL: BASE_URL,
546
- aiService: aiService,
547
- uploadService: uploadService
548
- });
549
-
550
-
551
- let start_promise_message= new Date();
552
- let message = await new Promise(async (resolve, reject) => {
553
- winston.debug('(voice) ******* user text -->'+ usertext)
554
- if(usertext === '' || !usertext){
555
-
556
- let start_time_get_message = new Date()
557
- let message = await getMessage(callSid, from, project_id, conversation_id)
558
- let end_time_get_message = new Date()
559
- winston.verbose(`(if) Time to getMessage from queue in /speechresult/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]`)
560
- resolve(message)
561
- }else{
562
- //CASE:usertext is not empty and queue is empty --> send message to tiledesk and manage delayTime
563
- let tiledeskMessage= {
564
- text:usertext,
565
- senderFullname: from,
566
- type: 'text',
567
- channel: { name: CHANNEL_NAME }
568
- };
569
-
570
- let start_time_send_message = new Date()
571
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
572
- let end_time_send_message = new Date()
573
- winston.debug("message sent : ", tdMessage);
574
- winston.verbose(`(else) Time to send message to tiledesk in /speechresult/${callSid} : ${(end_time_send_message-start_time_send_message)}[ms] with text ` + tdMessage.text + ' --- at time:' + new Date())
575
-
576
- let start_time_get_message = new Date()
577
- let message = await getMessage(callSid, from, project_id, conversation_id)
578
- let end_time_get_message = new Date()
579
- winston.verbose(`(else) Time to getMessage from queue in /speechresult/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]` + ' --- at time:' + new Date())
580
- resolve(message)
581
-
582
- //generate Tiledesk wait message
583
- // let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
584
- // let message = await tdChannel.generateWaitTdMessage(from, delayTime)
585
- // //update delayIndex for wait command message time
586
- // await voiceChannel.saveDelayIndexForCallId(callSid)
587
- // resolve(message)
588
- }
589
- });
590
- let end_promise_message = new Date()
591
- winston.verbose(`Time to manage message in Promise /speechresult/${callSid}: ${(end_promise_message-start_promise_message)}[ms]` + ' with text' + message.text + ' --- at time --' + new Date())
592
-
593
- // convert response to vxml
594
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
595
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
596
-
597
- let end_call = new Date()
598
- winston.info(`Time to respond to /speechresult/${callSid} : ${(end_call-start_call)} [ms]`)
599
-
600
- // Render the response as XML in reply to the webhook request
601
- res.set('Content-Type', 'application/xml');
602
- res.status(200).send(messageToVXML);
603
- })
604
-
605
- /* ----> called with Record tag in action property <----- */
606
- router.post('/record/action/:callSid/',async (req, res) => {
607
- winston.verbose('+++++++++++(voice) called POST record/action/:callSid at time '+ new Date() + "at timestamp " + new Date().getTime());
608
- let start_call = new Date();
609
-
610
- let callSid = req.body.CallSid;
611
- let project_id, conversation_id, user;
612
- let from, to;
613
-
614
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
615
- if (!sessionInfo) {
616
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
617
- }
618
- project_id = sessionInfo.project_id;
619
- from = sessionInfo.from;
620
- to = sessionInfo.to;
621
- conversation_id = sessionInfo.conversation_id;
622
- user = sessionInfo.user;
623
-
624
- let vxmlAttributes = {
625
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
626
- TTS_VOICE_NAME: VOICE_NAME,
627
- callSid: callSid,
628
- };
629
-
630
- const tdChannel = new TiledeskChannel({
631
- API_URL: API_URL,
632
- redis_client: redis_client
633
- })
634
- tdChannel.setProjectId(project_id)
635
-
636
- const tdTranslator = new TiledeskTwilioTranslator({
637
- BASE_URL: BASE_URL,
638
- aiService: aiService,
639
- uploadService: uploadService
640
- });
641
-
642
-
643
- let start_time_get_message = new Date()
644
- let message = await getMessage(callSid, from, project_id, conversation_id)
645
- winston.debug('message from getMessage in /record/action/: ', message)
646
- let end_time_get_message = new Date()
647
- winston.verbose(`Time to getMessage from queue in /record/action/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]` + ' --- at time:' + new Date())
648
-
649
-
650
- //generate Tiledesk wait message
651
- // let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
652
- // let message = await tdChannel.generateWaitTdMessage(from, delayTime)
653
- // //update delayIndex for wait command message time
654
- // await voiceChannel.saveDelayIndexForCallId(callSid)
655
-
656
- // convert response to vxml
657
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
658
- winston.debug("(voice) /record/action VXML to SEND: "+ messageToVXML);
659
-
660
- let end_call = new Date();
661
- winston.info(`Time to respond to /record/action/${callSid} : ${(end_call-start_call)}[ms]`)
662
- res.set('Content-Type', 'application/xml');
663
- res.status(200).send(messageToVXML);
664
-
665
- });
666
-
667
- /* ----> called with Record tag in recordingStatusCallback property <----- */
668
- router.post('/record/callback/:callSid/',async (req, res) => {
669
- winston.verbose('+++++++++++(voice) called POST record/callback/:callSid at time'+ new Date() + "at timestamp " + new Date().getTime());
670
- let start_call = new Date();
671
-
672
- let callSid = req.params.callSid || req.body.CallSid;
673
- let audioFileUrl = req.body.RecordingUrl;
674
- let audioFileDuration = req.body.RecordingDuration;
675
- let button_action = req.query.button_action ? '#' + req.query.button_action : '';
676
- let previousIntentName = req.query.intentName || '';
677
-
678
- let project_id, conversation_id, user;
679
- let from, to;
680
-
681
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
682
- if (!sessionInfo) {
683
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
684
- }
685
- project_id = sessionInfo.project_id;
686
- from = sessionInfo.from;
687
- to = sessionInfo.to;
688
- conversation_id = sessionInfo.conversation_id;
689
- user = sessionInfo.user;
690
-
691
- const tdChannel = new TiledeskChannel({
692
- API_URL: API_URL,
693
- redis_client: redis_client
694
- })
695
- tdChannel.setProjectId(project_id)
696
-
697
- const CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
698
- let settings = await db.get(CONTENT_KEY);
699
- if(!settings){
700
- return res.status(404).send({error: "VOICE Channel not already connected"})
701
- }
702
-
703
- let tiledeskMessage = null;
704
- // tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
705
-
706
- //SPEECH TO TEXT
707
- const attributes = await voiceChannel.getSettingsForCallId(callSid);
708
- winston.debug(`[VOICE] getting text message from STT: ${audioFileUrl}, model: ${attributes.STT_MODEL}`);
709
- // generateSTT ritorna sempre un oggetto coerente (anche vuoto o /close)
710
- tiledeskMessage = await generateSTT(audioFileUrl, attributes, sessionInfo, settings)
711
- winston.debug('[VOICE] tiledeskMessage from STT: ', tiledeskMessage)
712
- if (!tiledeskMessage || Object.keys(tiledeskMessage).length === 0) {
713
- winston.debug(`[VOICE] STT result empty, fallback to no_input branch for callSid ${callSid}`);
714
- tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
715
- }else {
716
- const normalizedText = utils.normalizeSTT(tiledeskMessage.text);
717
- winston.verbose(`[VOICE] normalized STT text: ${normalizedText} for callSid ${callSid}`);
718
- if(!normalizedText){
719
- tiledeskMessage = buildNoInputMessage('no_input', { from, button_action, payload: { event: 'no_input', lastBlock: previousIntentName, lastTimestamp: Date.now()} });
720
- }else{
721
- tiledeskMessage.text = normalizedText;
722
- }
723
- }
724
-
725
- //send message to tiledesk
726
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
727
- let end_call = new Date();
728
- winston.info(`Time to respond to /record/callback/${callSid} : ${(end_call-start_call)} [ms] with text ` + tiledeskMessage.text);
729
-
730
- res.status(200).send({ success: true , message: "Message sent to Tiledesk for callSid " + callSid});
731
- })
732
-
733
-
734
- router.post('/menublock/:callSid', async (req, res) => {
735
- let start_call = new Date().getTime();
736
- winston.debug("(voice) called POST /menu", req.body);
737
- winston.debug("(voice) called POST /menu query" , req.query);
738
- winston.verbose('/menublock at: ' + new Date() + 'with text:'+ req.body.Digits)
739
-
740
- let message_text = '';
741
- let attributes = {};
742
- let button = {}
743
-
744
- let callSid = req.params.callSid;
745
- let buttons_menu = req.query.menu_options;
746
- let buttonNoMatch = req.query.button_action;
747
- let previousIntentName = req.query.intentName;
748
-
749
- let menu_choice = req.body.Digits || '';
750
-
751
- /** use case: DTMF MENU **/
752
- if(buttons_menu){
753
- buttons_menu.split(';').some((option)=> {
754
- option = option.split(':')
755
- if(option[0] === menu_choice){
756
- button.value = option[0]
757
- button.action = '#' + option[1]
758
- return true;
759
- }
760
- })
761
-
762
-
763
- /* case noMatch input: Digits is not in a valid menu option*/
764
- if(Object.keys(button).length === 0){
765
- button.value = menu_choice
766
- button.action = '#' + buttonNoMatch
767
- }
768
-
769
- message_text = button.value.toString();
770
- attributes = {
771
- action: button.action
772
- }
773
-
774
- }else{
775
- /** use case: DTMF Speech **/
776
- message_text = menu_choice.toString(); //.replace(/(\d)/g, '$1 '); //convert number to string and then add a space after each number
777
- }
778
-
779
- winston.debug("(voice) button menu: ", button);
780
- winston.debug("(voice) message_text menu: "+ message_text);
781
-
782
- let project_id, conversation_id, user;
783
- let from, to;
784
-
785
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
786
- if (!sessionInfo) {
787
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
788
- }
789
- project_id = sessionInfo.project_id;
790
- from = sessionInfo.from;
791
- to = sessionInfo.to;
792
- conversation_id = sessionInfo.conversation_id;
793
- user = sessionInfo.user;
794
-
795
- let vxmlAttributes = {
796
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
797
- TTS_VOICE_NAME: VOICE_NAME,
798
- callSid: callSid,
799
- };
800
-
801
- const tdChannel = new TiledeskChannel({
802
- API_URL: API_URL,
803
- redis_client: redis_client
804
- })
805
- tdChannel.setProjectId(project_id);
806
-
807
- const tdTranslator = new TiledeskTwilioTranslator({
808
- BASE_URL: BASE_URL,
809
- aiService: aiService,
810
- uploadService: uploadService
811
- });
812
-
813
- //send message to tiledesk
814
- let tiledeskMessage= {
815
- text:message_text,
816
- senderFullname: from,
817
- type: 'text',
818
- channel: { name: CHANNEL_NAME },
819
- attributes: attributes
820
- };
821
- let response = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
822
- if(!response){
823
- return res.status(503).send({ message: "Bad response: Quota exceeded" })
824
- }
825
-
826
- let start_time_get_message = new Date()
827
- let message = await getMessage(callSid, from, project_id, conversation_id)
828
- let end_time_get_message = new Date()
829
- winston.verbose(`Time to getMessage from queue in /menublock/${callSid} : ${(end_time_get_message-start_time_get_message)}[ms]` + ' --- at time:' + new Date())
830
-
831
- // //generate Tiledesk wait message
832
- // let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
833
- // let message = await tdChannel.generateWaitTdMessage(from, delayTime)
834
- // //update delayIndex for wait command message time
835
- // await voiceChannel.saveDelayIndexForCallId(callSid)
836
-
837
- // convert response to vxml
838
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
839
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
840
-
841
- let end_call = new Date().getTime();
842
- winston.info(`Time to respond to /menublock/${callSid} : ${(end_call-start_call)} [ms]`)
843
-
844
- res.set('Content-Type', 'application/xml');
845
- res.status(200).send(messageToVXML);
846
- });
847
-
848
- router.post('/handle/:callSid/:event', async (req, res) => {
849
- winston.debug("(voice) called POST /handle", req.body);
850
- winston.debug("(voice) called POST /handle query -->", req.query);
851
- winston.debug("(voice) called POST /handle params-->", req.params);
852
-
853
- let event = req.params.event;
854
- let callSid = req.params.callSid;
855
- let button_action = '#' + req.query.button_action;
856
- let previousIntentName = req.query.intentName;
857
-
858
- let project_id, conversation_id, user;
859
- let from, to;
860
-
861
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
862
- if (!sessionInfo) {
863
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
864
- }
865
- project_id = sessionInfo.project_id;
866
- from = sessionInfo.from;
867
- to = sessionInfo.to;
868
- conversation_id = sessionInfo.conversation_id;
869
- user = sessionInfo.user;
870
-
871
- let vxmlAttributes = {
872
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
873
- TTS_VOICE_NAME: VOICE_NAME,
874
- callSid: callSid,
875
- };
876
-
877
- const tdChannel = new TiledeskChannel({
878
- API_URL: API_URL,
879
- redis_client: redis_client
880
- })
881
- tdChannel.setProjectId(project_id);
882
-
883
- const tdTranslator = new TiledeskTwilioTranslator({
884
- BASE_URL: BASE_URL,
885
- aiService: aiService,
886
- uploadService: uploadService
887
- });
888
-
889
-
890
- //send message to tiledesk
891
- let tiledeskMessage= {
892
- text: "/" + event,
893
- senderFullname: from,
894
- type: 'text',
895
- channel: { name: CHANNEL_NAME },
896
- attributes: {
897
- type: 'info',
898
- action: button_action,
899
- payload: {
900
- event: event,
901
- lastBlock: previousIntentName,
902
- lastTimestamp: Date.now()
903
- }
904
- }
905
- };
906
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
907
-
908
- //generate Tiledesk wait message
909
- let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
910
- let message = await tdChannel.generateWaitTdMessage(callSid, delayTime)
911
- ///update delayIndex for wait command message time
912
- await voiceChannel.saveDelayIndexForCallId(callSid)
913
-
914
- // convert response to vxml
915
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes,sessionInfo)
916
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
917
-
918
- res.set('Content-Type', 'application/xml');
919
- res.status(200).send(messageToVXML);
920
-
921
- });
922
-
923
-
924
- /* ----> catch Event block <----- */
925
- router.post('/event/:callSid/:event', async(req, res)=> {
926
- winston.debug("(voice) called POST /event" , req.params);
927
- winston.debug("(voice) called POST /event query" , req.query);
928
- winston.debug("(voice) called POST /event body" , req.body);
929
-
930
- //clear getMessage function timeout
931
- if(messageTimeout){
932
- clearTimeout(messageTimeout)
933
- }
934
-
935
- let event = req.params.event;
936
- let callSid = req.params.callSid;
937
- let currentIntentName = req.query.intentName;
938
- let currentIntentTimestamp = req.query.previousIntentTimestamp;
939
-
940
- let project_id, conversation_id, user;
941
- let from, to;
942
-
943
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
944
- if (!sessionInfo) {
945
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
946
- }
947
- project_id = sessionInfo.project_id;
948
- from = sessionInfo.from;
949
- to = sessionInfo.to;
950
- conversation_id = sessionInfo.conversation_id;
951
- user = sessionInfo.user;
952
-
953
- let vxmlAttributes = {
954
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
955
- TTS_VOICE_NAME: VOICE_NAME,
956
- callSid: callSid,
957
- };
958
-
959
- const tdChannel = new TiledeskChannel({
960
- API_URL: API_URL,
961
- redis_client: redis_client
962
- })
963
- tdChannel.setProjectId(project_id);
964
-
965
- const tdTranslator = new TiledeskTwilioTranslator({
966
- BASE_URL: BASE_URL,
967
- aiService: aiService,
968
- uploadService: uploadService
969
- });
970
-
971
- let button_action = ''
972
- if(event === 'transfer'){
973
- let callStatus = req.body.CallStatus;
974
- switch(callStatus){
975
- case CALL_STATUS.COMPLETED:
976
- button_action = '#' + req.query.button_success;
977
- break;
978
- case CALL_STATUS.FAILED:
979
- button_action = '#' + req.query.button_failure;
980
- break;
981
- }
982
-
983
- switch(callStatus){
984
- case CALL_STATUS.COMPLETED:
985
- case CALL_STATUS.FAILED: {
986
- //send message to tiledesk
987
- let tiledeskMessage= {
988
- //text:'\\close',
989
- text:'/'+event,
990
- senderFullname: from,
991
- type: 'text',
992
- channel: { name: CHANNEL_NAME },
993
- attributes: {
994
- subtype: "info",
995
- action: button_action,
996
- payload: {
997
- event: event,
998
- lastBlock: currentIntentName,
999
- lastTimestamp: currentIntentTimestamp
1000
- }
1001
- }
1002
- };
1003
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
1004
- }
1005
- }
1006
-
1007
- }
1008
-
1009
-
1010
- //generate Tiledesk wait message
1011
- let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid)
1012
- let message = await tdChannel.generateWaitTdMessage(callSid, delayTime)
1013
- ///update delayIndex for wait command message time
1014
- await voiceChannel.saveDelayIndexForCallId(callSid)
1015
-
1016
- // convert response to vxml
1017
- let messageToVXML = await tdTranslator.toVXML(message, callSid, vxmlAttributes, sessionInfo)
1018
- winston.debug("(voice) VXML to SEND: "+ messageToVXML);
1019
-
1020
- res.set('Content-Type', 'application/xml');
1021
- res.status(200).send(messageToVXML);
1022
- })
1023
-
1024
-
1025
- /* ----> catch Twilio Events <----- */
1026
- router.post('/twilio/status',async (req, res) => {
1027
- winston.debug('+++++++++++(voice) called POST twilio/status ', req.body);
1028
-
1029
- let event = req.body.CallStatus;
1030
- let callSid = req.body.CallSid;
1031
-
1032
- //clear getMessage function timeout
1033
- if(messageTimeout){
1034
- clearTimeout(messageTimeout)
1035
- }
1036
-
1037
- let project_id, conversation_id, user;
1038
- let from, to;
1039
-
1040
- let sessionInfo = await voiceChannel.getSessionForCallId(callSid)
1041
- if (!sessionInfo) {
1042
- return res.status(500).send({ success: "false", message: "Can't retrive data for callSid ->" + callSid });
1043
- }
1044
- project_id = sessionInfo.project_id;
1045
- from = sessionInfo.from;
1046
- to = sessionInfo.to;
1047
- conversation_id = sessionInfo.conversation_id;
1048
- user = sessionInfo.user;
1049
-
1050
- let vxmlAttributes = {
1051
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
1052
- TTS_VOICE_NAME: VOICE_NAME,
1053
- callSid: callSid,
1054
- };
1055
-
1056
- const tdChannel = new TiledeskChannel({
1057
- API_URL: API_URL,
1058
- redis_client: redis_client
1059
- })
1060
- tdChannel.setProjectId(project_id)
1061
-
1062
- const tdTranslator = new TiledeskTwilioTranslator({
1063
- BASE_URL: BASE_URL,
1064
- aiService: aiService,
1065
- uploadService: uploadService
1066
- });
1067
-
1068
-
1069
- switch(event){
1070
- case CALL_STATUS.COMPLETED:{
1071
- //send message to tiledesk
1072
- let tiledeskMessage= {
1073
- //text:'\\close',
1074
- text:'/close',
1075
- senderFullname: from,
1076
- type: 'text',
1077
- channel: { name: CHANNEL_NAME },
1078
- attributes: {
1079
- subtype: "info",
1080
- action: 'close'+JSON.stringify({event: event}),
1081
- payload: {
1082
- catchEvent: event
1083
- },
1084
- timestamp: 'xxxxxx'
1085
- }
1086
- };
1087
-
1088
- let tdMessage = await tdChannel.send(tiledeskMessage, user.token, conversation_id);
1089
-
1090
- //remove session data for current callId and relative queue data
1091
- await voiceChannel.deleteCallKeys(callSid);
1092
- await tdChannel.clearQueue(conversation_id);
1093
- break;
1094
- }
1095
- }
1096
-
1097
- res.status(200).send();
1098
-
1099
- })
1100
-
1101
-
1102
-
1103
- router.post('/twilio/fail',async (req, res) => {
1104
- winston.debug('+++++++++++(voice) called POST twilio/fail ', req.params)
1105
- winston.debug('+++++++++++(voice) called POST twilio/fail ', req.body)
1106
-
1107
- res.set('Content-Type', 'application/xml');
1108
- res.status(200).send('<Response></Response>');
1109
- })
1110
-
1111
-
1112
-
1113
- async function generateSTT(audioFileUrl, attributes, sessionInfo, settings){
1114
-
1115
- winston.debug("(voice) generateSTT: "+ attributes.VOICE_PROVIDER);
1116
-
1117
- let tiledeskMessage = {};
1118
- let text = null;
1119
-
1120
- try {
1121
- switch(attributes.VOICE_PROVIDER){
1122
- case VOICE_PROVIDER.OPENAI: {
1123
- let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
1124
- let publicKey = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.publicKey
1125
- if(publicKey){
1126
- let keep_going = await aiService.checkQuoteAvailability(sessionInfo.project_id, settings.token)
1127
- winston.debug('(voice) checkQuoteAvailability return: '+ keep_going);
1128
- if(!keep_going){
1129
- //no token is available --> close conversation
1130
- return tiledeskMessage= {
1131
- //text:'\\close',
1132
- text:'/close',
1133
- senderFullname: sessionInfo.from,
1134
- type: 'text',
1135
- channel: { name: CHANNEL_NAME },
1136
- attributes: {
1137
- subtype: "info",
1138
- action: 'close'+JSON.stringify({event: 'quota_exceeded'}),
1139
- payload: {
1140
- catchEvent: 'quota_exceeded'
1141
- },
1142
- timestamp: 'xxxxxx'
1143
- }
1144
- };
1145
-
1146
- }
1147
- }
1148
-
1149
- text = await aiService.speechToText(audioFileUrl, attributes.STT_MODEL, GPT_KEY)
1150
- break;
1151
- }
1152
- case VOICE_PROVIDER.ELEVENLABS: {
1153
- let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
1154
- const ttsLanguage = attributes.TTS_LANGUAGE || 'en';
1155
- text = await aiService.speechToTextElevenLabs( audioFileUrl, attributes.STT_MODEL, ttsLanguage, ELEVENLABS_APIKEY )
1156
- break;
1157
- }
1158
- default:
1159
- throw new Error('Unsupported VOICE_PROVIDER: ' + attributes.VOICE_PROVIDER);
1160
- }
1161
-
1162
- if(text){
1163
- winston.debug('[STT] text empty → fallback no_input');
1164
- tiledeskMessage = {
1165
- text: text,
1166
- senderFullname: sessionInfo.from,
1167
- type: 'text',
1168
- channel: { name: CHANNEL_NAME }
1169
- };
1170
- }
1171
- } catch (error) {
1172
- winston.error('[STT] generateSTT error:', error);
1173
- switch (error.code) {
1174
- case 'AISERVICE_FAILED':
1175
- winston.error('[STT] AISERVICE_FAILED → ', error.message);
1176
- break;
1177
- }
1178
-
1179
- // fallback: tiledeskMessage vuoto
1180
- tiledeskMessage = {};
1181
-
1182
- }
1183
-
1184
- return tiledeskMessage
1185
- }
1186
-
1187
-
1188
- async function buildNoInputMessage(event, { from, button_action, payload }) {
1189
- return {
1190
- text: `/${event}`,
1191
- senderFullname: from,
1192
- type: 'text',
1193
- channel: { name: CHANNEL_NAME },
1194
- attributes: {
1195
- type: 'info',
1196
- action: button_action,
1197
- payload: payload
1198
- }
1199
- };
1200
- }
1201
-
1202
-
1203
-
1204
- router.get('/addon/transcript', async (req, res) => {
1205
- winston.debug("(voice) called GET /transcript query-->" , req.query);
1206
- winston.debug("(voice) called GET /transcript body -->" , req.body);
1207
-
1208
- res.status(200).send('ok');
1209
-
1210
- });
1211
-
1212
- /** --> only for test purpose <-- **/
1213
- router.get('/test', async (req, res) => {
1214
- winston.debug("(voice) called GET /test" , req.query);
1215
-
1216
- let project_id = req.query.id_project;
1217
- let callSid = req.body.CallSid;
1218
- let from = req.body.From;
1219
- let to = req.body.To;
1220
-
1221
- if(!from || !to){
1222
- return res.status(404).send({error: "Error: Missing from/to parameters"})
1223
- }
1224
- from = utils.getNumber(from); //remove '+' from number
1225
- to = utils.getNumber(to); //remove '+' from number
1226
-
1227
- const tdChannel = new TiledeskChannel({
1228
- API_URL: API_URL,
1229
- redis_client: redis_client,
1230
- });
1231
- tdChannel.setProjectId(project_id)
1232
-
1233
- const tdTranslator = new TiledeskTwilioTranslator({
1234
- BASE_URL: BASE_URL,
1235
- aiService: aiService,
1236
- uploadService: uploadService
1237
- });
1238
-
1239
- //let result = await tdChannel.signIn(ani)
1240
-
1241
- //let conversation_id = await tdChannel.getConversation(ani, callId, result.token);
1242
- //let message = await tdChannel.sendMessageAndWait(ani, result.chat21.user_data, conversation_id, '/start');
1243
- let delayTime = await voiceChannel.getNextDelayTimeForCallId(callSid);
1244
- let waitTiledeskMessage = await tdChannel.generateWaitTdMessage(from, delayTime);
1245
-
1246
- //console.log("response message: ", message);
1247
- let message2= {
1248
- attributes: {
1249
- commands: [
1250
- {
1251
- type: 'wait',
1252
- time: 500
1253
- },
1254
- {
1255
- type: 'message',
1256
- message: {
1257
- text: 'ciao',
1258
- }
1259
- }
1260
- ]
1261
- }
1262
- }
1263
-
1264
- let message_buttons = {
1265
- attributes: {
1266
- disableInputMessage: true,
1267
- commands: [
1268
- {
1269
- type: 'wait',
1270
- time: 500
1271
- },
1272
- {
1273
- type: 'message',
1274
- message: {
1275
- text: 'ciao',
1276
- type: 'text',
1277
- attributes: {
1278
- attachment: {
1279
- buttons: [
1280
- {value: 'button1', type: 'action', target: 'blank', action: '#action1'},
1281
- {value: 'button2', type: 'action', target: 'blank', action: '#action2'}
1282
- ]
1283
- }
1284
- }
1285
- }
1286
- },
1287
- {
1288
- type: 'wait',
1289
- time: 500
1290
- },
1291
- {
1292
- type: 'message',
1293
- message: {
1294
- text: 'ciao 2',
1295
- type: 'frame',
1296
- metadata: {
1297
- src:"https://your-audio-url.com"
1298
- }
1299
- }
1300
- },
1301
- {
1302
- type: 'wait',
1303
- time: 0
1304
- },
1305
- /*{
1306
- type: 'settings',
1307
- subType: 'blind_transfer',
1308
- settings: {
1309
- transferTo: "6040",
1310
- transferType: "consultation",
1311
- falseIntent: "#falseIntenttt",
1312
- trueIntent: "#trueIntennt"
1313
- }
1314
- }*/
1315
- /*{
1316
- type: 'settings',
1317
- subType: 'dtmf_form',
1318
- settings: {
1319
- minDigits: 5,
1320
- maxDigits: 5,
1321
- terminators: "#",
1322
- noInputIntent: "#a",
1323
- noInputTimeout: 2000,
1324
- }
1325
- }*/
1326
- {
1327
- type: 'settings',
1328
- subType: 'speech_form',
1329
- settings: {
1330
- noInputIntent: "#a",
1331
- noInputTimeout: 2000,
1332
- }
1333
- }
1334
- ]
1335
- }
1336
- }
1337
-
1338
-
1339
- let vxmlAttributes = {
1340
- TTS_VOICE_LANGUAGE: VOICE_LANGUAGE,
1341
- TTS_VOICE_NAME: VOICE_NAME,
1342
- callSid: callSid
1343
- };
1344
-
1345
- let sessionInfo = {
1346
- user: {
1347
- _id: 'id_user'
1348
- },
1349
- integrations: [
1350
- { type: 'openai', key: GPT_KEY}
1351
- ]
1352
- }
1353
-
1354
-
1355
- // convert response to vxml
1356
- let messageToVXML = await tdTranslator.toVXML(message_buttons, callSid, vxmlAttributes, sessionInfo )
1357
- //let disconnect = await tdChannel.disconnect();
1358
-
1359
-
1360
- res.set('Content-Type', 'application/xml');
1361
- res.status(200).send(messageToVXML);
1362
-
1363
- //res.status(200).send(message);
1364
-
1365
- })
1366
-
1367
-
1368
- async function connectRedis() {
1369
- /*redis_client = await redis.createClient({
1370
- host: REDIS_HOST,
1371
- port: REDIS_PORT,
1372
- password: REDIS_PASSWORD
1373
- });*/
1374
-
1375
- if(REDIS_PASSWORD){
1376
- redis_client = await redis.createClient({
1377
- socket: {
1378
- host: REDIS_HOST,
1379
- port: REDIS_PORT
1380
- },
1381
- password: REDIS_PASSWORD,
1382
- })
1383
- }else {
1384
- redis_client = await redis.createClient({
1385
- url: 'redis://'+REDIS_HOST+':'+REDIS_PORT
1386
- })
1387
- }
1388
-
1389
-
1390
- redis_client.on('error', err => {
1391
- winston.error('(voice) Connect Redis Error ' + err);
1392
- })
1393
- /*
1394
- redis_client.on('connect', () => {
1395
- winston.debug('Redis Connected!'); // Connected!
1396
- });
1397
- */
1398
- redis_client.on('ready', () => {
1399
- winston.debug("(voice) Redis ready!")
1400
- })
1401
- await redis_client.connect(); // only for v4
1402
- require('bluebird').promisifyAll(redis_client)
1403
-
1404
- }
1405
-
1406
- async function startApp(settings, callback) {
1407
- winston.info("(voice) Starting VOICE TWILIO App");
1408
-
1409
- if (!settings.MONGODB_URI) {
1410
- winston.error("(voice) MONGODB_URI is mandatory. Exit...");
1411
- return callback('Missing parameter: MONGODB_URI');
1412
- }
1413
-
1414
- if (!settings.API_URL) {
1415
- let port = process.env.PORT || 3000;
1416
- API_URL = 'http://localhost:' + port;
1417
- } else {
1418
- API_URL = settings.API_URL;
1419
- }
1420
- winston.debug("(voice) API_URL: " + API_URL)
1421
-
1422
- if (!settings.BASE_FILE_URL) {
1423
- let port = process.env.PORT || 3000;
1424
- BASE_FILE_URL = 'http://localhost:' + port;
1425
- } else {
1426
- BASE_FILE_URL = settings.BASE_FILE_URL;
1427
- }
1428
- winston.debug("(voice) API_URL: " + API_URL)
1429
-
1430
- if (!settings.BASE_URL) {
1431
- winston.error("(voice) BASE_URL is mandatory. Exit...");
1432
- return callback('Missing parameter: BASE_URL');
1433
- } else {
1434
- BASE_URL = settings.BASE_URL;
1435
- winston.debug("(voice) BASE_URL: " + BASE_URL);
1436
- }
1437
-
1438
- if (settings.BRAND_NAME) {
1439
- BRAND_NAME = settings.BRAND_NAME
1440
- }
1441
-
1442
- if (settings.GPT_KEY) {
1443
- GPT_KEY = settings.GPT_KEY;
1444
- }
1445
-
1446
- if(settings.OPENAI_ENDPOINT){
1447
- OPENAI_ENDPOINT = settings.OPENAI_ENDPOINT
1448
- }
1449
- if(settings.ELEVENLABS_ENDPOINT){
1450
- ELEVENLABS_ENDPOINT = settings.ELEVENLABS_ENDPOINT
1451
- }
1452
-
1453
- if(settings.MAX_POLLING_TIME){
1454
- MAX_POLLING_TIME = settings.MAX_POLLING_TIME/1000; //convert in seconds
1455
- }
1456
-
1457
- if (settings.REDIS_HOST && settings.REDIS_PORT) {
1458
- REDIS_HOST = settings.REDIS_HOST;
1459
- REDIS_PORT = settings.REDIS_PORT;
1460
- REDIS_PASSWORD = settings.REDIS_PASSWORD;
1461
- await connectRedis();
1462
- } else {
1463
- winston.error("(voice) Missing redis parameters --> REDIS_HOST and REDIS_PORT");
1464
- }
1465
-
1466
- //init VOICE CHANNEL
1467
- voiceChannel = new VoiceChannel({
1468
- BASE_POOLING_DELAY: settings.BASE_POOLING_DELAY || BASE_POOLING_DELAY,
1469
- redis_client: redis_client,
1470
- })
1471
-
1472
- //init Services
1473
- aiService = new AiService({
1474
- OPENAI_ENDPOINT: OPENAI_ENDPOINT,
1475
- ELEVENLABS_ENDPOINT: ELEVENLABS_ENDPOINT,
1476
- API_URL: API_URL
1477
- })
1478
- integrationService = new IntegrationService({
1479
- API_URL: API_URL,
1480
- })
1481
- uploadService = new UploadService({
1482
- API_URL: BASE_FILE_URL,
1483
- })
1484
-
1485
-
1486
- if (settings.dbconnection) {
1487
- db.reuseConnection(settings.dbconnection, () => {
1488
- winston.debug("(voice) KVBaseMongo reused exsisting db connection");
1489
- if (callback) {
1490
- callback(null);
1491
- }
1492
- })
1493
- } else {
1494
- db.connect(settings.MONGODB_URI, () => {
1495
- winston.debug("(voice) KVBaseMongo successfully connected.");
1496
-
1497
- if (callback) {
1498
- callback(null);
1499
- }
1500
- });
1501
- }
1502
-
1503
- manageApp.startApp({
1504
- API_URL: API_URL,
1505
- BASE_URL: BASE_URL,
1506
- BRAND_NAME: BRAND_NAME,
1507
- DB: db,
1508
- redis_client: redis_client
1509
- }, (err) => {
1510
- if (!err) {
1511
- winston.info("Manage route succesfully started.");
1512
- } else {
1513
- winston.error("(voice) Unable to start API route.")
1514
- }
1515
- })
1516
-
1517
- }
1518
-
1519
- function readHTMLFile(templateName, callback) {
1520
- fs.readFile(__dirname + '/template' + templateName, { encoding: 'utf-8' },
1521
- function(err, html) {
1522
- if (err) {
1523
- throw err;
1524
- //callback(err);
1525
- } else {
1526
- callback(null, html)
1527
- }
1528
- })
1529
- }
1530
-
1531
- module.exports = { router: router, startApp: startApp };
6
+ // Auto-start server when run directly (standalone mode)
7
+ if (require.main === module) {
8
+ startServer();
9
+ }