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