@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.
- package/LICENSE +179 -0
- package/README.md +44 -0
- package/index.js +7 -1562
- package/package.json +23 -22
- package/src/app.js +146 -0
- package/src/config/index.js +32 -0
- package/src/controllers/VoiceController.js +488 -0
- package/src/controllers/VoiceController.original.js +811 -0
- package/src/middlewares/httpLogger.js +31 -0
- package/src/models/KeyValueStore.js +78 -0
- package/src/routes/manageApp.js +298 -0
- package/src/routes/voice.js +22 -0
- package/src/services/AiService.js +219 -0
- package/src/services/AiService.sdk.js +367 -0
- package/src/services/IntegrationService.js +74 -0
- package/src/services/MessageService.js +133 -0
- package/src/services/README_SDK.md +107 -0
- package/src/services/SessionService.js +143 -0
- package/src/services/SpeechService.js +134 -0
- package/src/services/TiledeskMessageBuilder.js +135 -0
- package/src/services/TwilioService.js +122 -0
- package/src/services/UploadService.js +78 -0
- package/src/services/channels/TiledeskChannel.js +269 -0
- package/{tiledesk → src/services/channels}/VoiceChannel.js +17 -56
- package/src/services/clients/TiledeskSubscriptionClient.js +78 -0
- package/src/services/index.js +45 -0
- package/src/services/translators/TiledeskTwilioTranslator.js +509 -0
- package/{tiledesk/TiledeskTwilioTranslator.js → src/services/translators/TiledeskTwilioTranslator.original.js} +119 -212
- package/src/utils/fileUtils.js +24 -0
- package/src/utils/logger.js +32 -0
- package/{tiledesk → src/utils}/utils-message.js +6 -21
- package/logs/app.log +0 -3082
- package/routes/manageApp.js +0 -419
- package/tiledesk/KVBaseMongo.js +0 -101
- package/tiledesk/TiledeskChannel.js +0 -363
- package/tiledesk/TiledeskSubscriptionClient.js +0 -135
- package/tiledesk/fileUtils.js +0 -55
- package/tiledesk/services/AiService.js +0 -230
- package/tiledesk/services/IntegrationService.js +0 -81
- package/tiledesk/services/UploadService.js +0 -88
- /package/{winston.js → src/config/logger.js} +0 -0
- /package/{tiledesk → src}/services/voiceEventEmitter.js +0 -0
- /package/{template → src/template}/configure.html +0 -0
- /package/{template → src/template}/css/configure.css +0 -0
- /package/{template → src/template}/css/error.css +0 -0
- /package/{template → src/template}/css/style.css +0 -0
- /package/{template → src/template}/error.html +0 -0
- /package/{tiledesk → src/utils}/constants.js +0 -0
- /package/{tiledesk → src/utils}/errors.js +0 -0
- /package/{tiledesk → src/utils}/utils.js +0 -0
package/index.js
CHANGED
|
@@ -1,1564 +1,9 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|