@tiledesk/tiledesk-voice-twilio-connector 0.1.27 → 0.2.0-rc3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +179 -0
- package/README.md +44 -0
- package/index.js +7 -1529
- 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 -202
- 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
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
const { v4: uuidv4 } = require("uuid");
|
|
2
|
-
const winston = require("
|
|
2
|
+
const winston = require("../../utils/logger");
|
|
3
3
|
const xmlbuilder = require("xmlbuilder");
|
|
4
4
|
const querystring = require("querystring");
|
|
5
5
|
|
|
6
|
-
const utils = require("
|
|
7
|
-
const utils_message = require("
|
|
8
|
-
const MENU_CHOICE = require("
|
|
9
|
-
const WAIT_MESSAGE = require("
|
|
10
|
-
const TEXT_MESSAGE = require("
|
|
11
|
-
const SETTING_MESSAGE = require('
|
|
12
|
-
const CHANNEL_NAME = require('
|
|
13
|
-
const VOICE_PROVIDER = require('
|
|
14
|
-
const OPENAI_SETTINGS = require('
|
|
15
|
-
const ELEVENLABS_SETTINGS = require('
|
|
6
|
+
const utils = require("../../utils/utils.js");
|
|
7
|
+
const utils_message = require("../../utils/utils-message.js");
|
|
8
|
+
const MENU_CHOICE = require("../../utils/constants.js").MENU_CHOICE;
|
|
9
|
+
const WAIT_MESSAGE = require("../../utils/constants.js").WAIT_MESSAGE;
|
|
10
|
+
const TEXT_MESSAGE = require("../../utils/constants.js").TEXT_MESSAGE;
|
|
11
|
+
const SETTING_MESSAGE = require('../../utils/constants').SETTING_MESSAGE
|
|
12
|
+
const CHANNEL_NAME = require('../../utils/constants').CHANNEL_NAME
|
|
13
|
+
const VOICE_PROVIDER = require('../../utils/constants').VOICE_PROVIDER;
|
|
14
|
+
const OPENAI_SETTINGS = require('../../utils/constants').OPENAI_SETTINGS;
|
|
15
|
+
const ELEVENLABS_SETTINGS = require('../../utils/constants').ELEVENLABS_SETTINGS;
|
|
16
16
|
|
|
17
|
-
const TYPE_ACTION_VXML = require('
|
|
18
|
-
const TYPE_MESSAGE = require('
|
|
19
|
-
const INFO_MESSAGE_TYPE = require('
|
|
17
|
+
const TYPE_ACTION_VXML = require('../../utils/constants').TYPE_ACTION_VXML
|
|
18
|
+
const TYPE_MESSAGE = require('../../utils/constants').TYPE_MESSAGE
|
|
19
|
+
const INFO_MESSAGE_TYPE = require('../../utils/constants').INFO_MESSAGE_TYPE
|
|
20
20
|
|
|
21
|
-
const voiceEventEmitter = require('
|
|
21
|
+
const voiceEventEmitter = require('../voiceEventEmitter');
|
|
22
22
|
|
|
23
|
-
const { SttError } = require('
|
|
23
|
+
const { SttError } = require('../../utils/errors');
|
|
24
24
|
|
|
25
25
|
class TiledeskTwilioTranslator {
|
|
26
26
|
/**
|
|
@@ -52,7 +52,7 @@ class TiledeskTwilioTranslator {
|
|
|
52
52
|
this.uploadService = config.uploadService
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
this.lastCallSidVerb = {};
|
|
56
56
|
this.log = false;
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -115,8 +115,8 @@ class TiledeskTwilioTranslator {
|
|
|
115
115
|
vxmlAttributes = this.manageVoiceAttributes(msg, vxmlAttributes)
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
this.user = sessionInfo.user
|
|
119
|
-
this.integrations = sessionInfo.integrations
|
|
118
|
+
// this.user = sessionInfo.user
|
|
119
|
+
// this.integrations = sessionInfo.integrations
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
const xml = xmlbuilder.create("Response", {});
|
|
@@ -141,7 +141,7 @@ class TiledeskTwilioTranslator {
|
|
|
141
141
|
const isWait = this.checkIfIsWait(msg);
|
|
142
142
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isWait:"+ isWait);
|
|
143
143
|
if(isWait){
|
|
144
|
-
const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes);
|
|
144
|
+
const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
145
145
|
return delayForm;
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -149,7 +149,7 @@ class TiledeskTwilioTranslator {
|
|
|
149
149
|
const isDtmfForm = this.checkIfIsDTMFForm(msg);
|
|
150
150
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isDtmfForm: "+ isDtmfForm);
|
|
151
151
|
if(isDtmfForm){
|
|
152
|
-
const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes);
|
|
152
|
+
const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
153
153
|
return DTMFForm;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -157,7 +157,7 @@ class TiledeskTwilioTranslator {
|
|
|
157
157
|
const isBlindFransfer = this.checkIfIsBlindFransfer(msg);
|
|
158
158
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isBlindFransfer: "+ isBlindFransfer);
|
|
159
159
|
if(isBlindFransfer){
|
|
160
|
-
const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes);
|
|
160
|
+
const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
161
161
|
return blindTransfer;
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -165,7 +165,7 @@ class TiledeskTwilioTranslator {
|
|
|
165
165
|
const isMenu = this.checkIfIsDTMFMenuMessage(msg);
|
|
166
166
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isMenu: "+ isMenu);
|
|
167
167
|
if(isMenu){
|
|
168
|
-
const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes);
|
|
168
|
+
const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
169
169
|
return menu;
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -173,71 +173,62 @@ class TiledeskTwilioTranslator {
|
|
|
173
173
|
const isSpeechForm = this.checkIfIsSpeechFormMessage(msg);
|
|
174
174
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isSpeechForm: "+ isSpeechForm);
|
|
175
175
|
if(isSpeechForm){
|
|
176
|
-
const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes);
|
|
176
|
+
const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
177
177
|
return form;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/** check for FORM (PlayPrompt action) **/
|
|
181
181
|
winston.debug("[TiledeskVXMLTranslator] toVXML: isPrompt: true");
|
|
182
|
-
const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes);
|
|
182
|
+
const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
|
|
183
183
|
return prompt;
|
|
184
184
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Generic helper to check if message has a specific VXML action subType
|
|
191
|
+
* @param {Object} msg - The message object
|
|
192
|
+
* @param {string} subType - The subType to check for
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
195
|
+
_hasSettingsSubType(msg, subType) {
|
|
196
|
+
const commands = msg.attributes?.commands;
|
|
197
|
+
if (!commands) return false;
|
|
198
|
+
const settingsElement = commands.find((command) => command.type === SETTING_MESSAGE);
|
|
199
|
+
return settingsElement?.subType === subType;
|
|
200
|
+
}
|
|
201
|
+
|
|
189
202
|
checkIfIsDTMFMenuMessage(msg) {
|
|
190
|
-
|
|
191
|
-
let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
|
|
192
|
-
if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_MENU){
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
203
|
+
return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_MENU);
|
|
196
204
|
}
|
|
197
205
|
|
|
198
206
|
checkIfIsDTMFForm(msg){
|
|
199
|
-
|
|
200
|
-
let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
|
|
201
|
-
if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_FORM){
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
return false;
|
|
207
|
+
return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_FORM);
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
checkIfIsBlindFransfer(msg){
|
|
208
|
-
|
|
209
|
-
let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
|
|
210
|
-
if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.BLIND_TRANSFER ){
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
return false;
|
|
211
|
+
return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.BLIND_TRANSFER);
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
checkIfIsSpeechFormMessage(msg){
|
|
217
|
-
|
|
218
|
-
let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
|
|
219
|
-
if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.SPEECH_FORM ){
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
return false;
|
|
215
|
+
return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.SPEECH_FORM);
|
|
223
216
|
}
|
|
224
217
|
|
|
225
218
|
checkIfIsWait(msg){
|
|
226
|
-
const commands = msg.attributes
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
return false;
|
|
219
|
+
const commands = msg.attributes?.commands;
|
|
220
|
+
if (!commands) return false;
|
|
221
|
+
const hasWait = commands.some((command) => command.type === WAIT_MESSAGE);
|
|
222
|
+
const hasMessage = commands.some((command) => command.type === TEXT_MESSAGE);
|
|
223
|
+
return hasWait && !hasMessage;
|
|
233
224
|
}
|
|
234
225
|
|
|
235
226
|
setVXMLAttributes(commands, attributes){
|
|
236
227
|
const settingsCommand = commands.slice(-1)[0];
|
|
237
|
-
if(settingsCommand
|
|
238
|
-
Object.
|
|
228
|
+
if(settingsCommand?.settings){
|
|
229
|
+
Object.assign(attributes, settingsCommand.settings);
|
|
239
230
|
}
|
|
240
|
-
return attributes
|
|
231
|
+
return attributes;
|
|
241
232
|
}
|
|
242
233
|
|
|
243
234
|
toTiledesk(vxmlMessage) {
|
|
@@ -245,20 +236,11 @@ class TiledeskTwilioTranslator {
|
|
|
245
236
|
}
|
|
246
237
|
|
|
247
238
|
getMessageFromTdMessage(msg) {
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
})*/
|
|
255
|
-
let command = msg.attributes.commands[1];
|
|
256
|
-
if (command.type === "message") {
|
|
257
|
-
text = command.message.text;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return text;
|
|
239
|
+
const commands = msg.attributes?.commands;
|
|
240
|
+
if (!commands || commands.length === 0) return "";
|
|
241
|
+
|
|
242
|
+
const command = commands[1];
|
|
243
|
+
return command?.type === "message" ? command.message.text : "";
|
|
262
244
|
}
|
|
263
245
|
|
|
264
246
|
getButtonsFromCommand(command) {
|
|
@@ -270,20 +252,16 @@ class TiledeskTwilioTranslator {
|
|
|
270
252
|
}
|
|
271
253
|
return buttons;
|
|
272
254
|
}
|
|
255
|
+
|
|
273
256
|
getButtonsFromTdMessage(msg) {
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
})*/
|
|
281
|
-
let command = msg.attributes.commands[1];
|
|
282
|
-
if (command.type === "message" && command.message.attributes && command.message.attributes.attachment && command.message.attributes.attachment.buttons ) {
|
|
283
|
-
buttons = command.message.attributes.attachment.buttons.filter( (button) => (button.type === "action") );
|
|
284
|
-
}
|
|
257
|
+
const commands = msg.attributes?.commands;
|
|
258
|
+
if (!commands || commands.length === 0) return [];
|
|
259
|
+
|
|
260
|
+
const command = commands[1];
|
|
261
|
+
if (command?.type === "message" && command.message?.attributes?.attachment?.buttons) {
|
|
262
|
+
return command.message.attributes.attachment.buttons.filter((button) => button.type === "action");
|
|
285
263
|
}
|
|
286
|
-
return
|
|
264
|
+
return [];
|
|
287
265
|
}
|
|
288
266
|
|
|
289
267
|
async hangupCall(rootEle){
|
|
@@ -293,10 +271,10 @@ class TiledeskTwilioTranslator {
|
|
|
293
271
|
}
|
|
294
272
|
|
|
295
273
|
|
|
296
|
-
async delayVXMLConverter(rootEle, message, xmlAttributes){
|
|
274
|
+
async delayVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
|
|
297
275
|
const command = message.attributes.commands[0]
|
|
298
276
|
|
|
299
|
-
const prompt = this.promptVXML(rootEle, message, xmlAttributes);
|
|
277
|
+
const prompt = this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
300
278
|
|
|
301
279
|
rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid).up()
|
|
302
280
|
|
|
@@ -304,12 +282,12 @@ class TiledeskTwilioTranslator {
|
|
|
304
282
|
}
|
|
305
283
|
|
|
306
284
|
|
|
307
|
-
async playPromptVXMLConverter(rootEle, message, xmlAttributes){
|
|
285
|
+
async playPromptVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
|
|
308
286
|
|
|
309
|
-
const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
|
|
287
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
310
288
|
|
|
311
|
-
const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
|
|
312
|
-
rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
|
|
289
|
+
// const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
|
|
290
|
+
// rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
|
|
313
291
|
//prompt.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/nextblock/' + session.connection.calltoken", method: "post", namelist: "usertext session intentName previousIntentTimestamp" });
|
|
314
292
|
|
|
315
293
|
return rootEle.end({ pretty: true });
|
|
@@ -317,7 +295,7 @@ class TiledeskTwilioTranslator {
|
|
|
317
295
|
}
|
|
318
296
|
|
|
319
297
|
/** DONE **/
|
|
320
|
-
async speechFormVXMLConverter(rootEle, message, xmlAttributes) {
|
|
298
|
+
async speechFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
321
299
|
|
|
322
300
|
if(this.voiceProvider === VOICE_PROVIDER.TWILIO){
|
|
323
301
|
const gather = rootEle.ele("Gather", { input: "speech"})
|
|
@@ -337,7 +315,7 @@ class TiledeskTwilioTranslator {
|
|
|
337
315
|
gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
|
|
338
316
|
}
|
|
339
317
|
|
|
340
|
-
const prompt = this.promptVXML(gather, message, xmlAttributes);
|
|
318
|
+
const prompt = this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
341
319
|
|
|
342
320
|
const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
|
|
343
321
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -346,7 +324,7 @@ class TiledeskTwilioTranslator {
|
|
|
346
324
|
|
|
347
325
|
}else{
|
|
348
326
|
|
|
349
|
-
const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
|
|
327
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
350
328
|
|
|
351
329
|
const record = rootEle.ele("Record", { playBeep: "false"})
|
|
352
330
|
|
|
@@ -374,7 +352,7 @@ class TiledeskTwilioTranslator {
|
|
|
374
352
|
}
|
|
375
353
|
|
|
376
354
|
/** DONE **/
|
|
377
|
-
async menuVXMLConverter(rootEle, message, xmlAttributes) {
|
|
355
|
+
async menuVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
378
356
|
const lastMessageCommand = message.attributes.commands.slice(-3)[0];
|
|
379
357
|
const options = this.getButtonsFromCommand(lastMessageCommand);
|
|
380
358
|
|
|
@@ -397,7 +375,7 @@ class TiledeskTwilioTranslator {
|
|
|
397
375
|
.att("method", "POST")
|
|
398
376
|
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
399
377
|
|
|
400
|
-
const prompt = await this.promptVXML(gather, message, xmlAttributes);
|
|
378
|
+
const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
401
379
|
|
|
402
380
|
|
|
403
381
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -408,7 +386,7 @@ class TiledeskTwilioTranslator {
|
|
|
408
386
|
}
|
|
409
387
|
|
|
410
388
|
/** DONE **/
|
|
411
|
-
async dtmfFormVXMLConverter(rootEle, message, xmlAttributes) {
|
|
389
|
+
async dtmfFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
412
390
|
|
|
413
391
|
const gather = rootEle.ele("Gather", { input: "dtmf"})
|
|
414
392
|
|
|
@@ -419,7 +397,7 @@ class TiledeskTwilioTranslator {
|
|
|
419
397
|
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
420
398
|
const settings = await this.optionsVXML(gather, message, xmlAttributes);
|
|
421
399
|
|
|
422
|
-
const prompt = this.promptVXML(gather, message, xmlAttributes);
|
|
400
|
+
const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
423
401
|
|
|
424
402
|
const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
|
|
425
403
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -431,12 +409,12 @@ class TiledeskTwilioTranslator {
|
|
|
431
409
|
}
|
|
432
410
|
|
|
433
411
|
|
|
434
|
-
async blindTransferVXMLConverter(rootEle, message, xmlAttributes) {
|
|
412
|
+
async blindTransferVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
435
413
|
const lastMessageCommand = message.attributes.commands.slice(-3)[0];
|
|
436
414
|
const options = this.getButtonsFromCommand(lastMessageCommand);
|
|
437
415
|
|
|
438
|
-
const prompt = this.promptVXML(rootEle, message, xmlAttributes);
|
|
439
|
-
const transfer = this.transferVXML(rootEle, message, xmlAttributes)
|
|
416
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
417
|
+
const transfer = await this.transferVXML(rootEle, message, xmlAttributes)
|
|
440
418
|
|
|
441
419
|
|
|
442
420
|
return rootEle.end({ pretty: true });
|
|
@@ -464,124 +442,63 @@ class TiledeskTwilioTranslator {
|
|
|
464
442
|
rootEle.ele("catch", { event: "connection.disconnect.hangup" })
|
|
465
443
|
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/hangup'", method: "post", namelist: "intentName previousIntentTimestamp"}).up();
|
|
466
444
|
rootEle.ele("catch", { event: "error.noresource.asr" })
|
|
467
|
-
//.ele("var", { name: "url", expr: "'https://tiledesk-vxml-connector.glitch.me/event" + "/no-resources?callId=" + attributes.callId + "'" }).up()
|
|
468
445
|
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/no-resources?event=' + application._event", method: "post", namelist: "intentName previousIntentTimestamp" }).up();
|
|
469
446
|
return rootEle;
|
|
470
447
|
}
|
|
471
448
|
|
|
472
449
|
|
|
473
450
|
async handleNoInputNoMatch(rootEle, msg, attributes){
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let dtmf_form_element = msg.attributes.commands.find((command) => command.type === SETTING_MESSAGE)
|
|
479
|
-
if(!dtmf_form_element){
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if(dtmf_form_element.settings.noInputIntent){
|
|
485
|
-
intent_noInput = dtmf_form_element.settings.noInputIntent
|
|
486
|
-
button_noIput = {
|
|
487
|
-
action: intent_noInput,
|
|
488
|
-
value: 'no_input'
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
queryNoInput = 'button_action='+button_noIput.action.substring(1);
|
|
493
|
-
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
/*element.ele("noinput")
|
|
497
|
-
.ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_INPUT + "'", }).up()
|
|
498
|
-
.ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noIput) + "'", }).up()
|
|
499
|
-
.ele("goto", { next: "#noinput_form" }).up()
|
|
500
|
-
.up()
|
|
501
|
-
|
|
502
|
-
rootEle.ele("form", { id: "noinput_form" })
|
|
503
|
-
.ele("block")
|
|
504
|
-
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_input'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
|
|
505
|
-
.up()
|
|
506
|
-
.up();
|
|
507
|
-
*/
|
|
508
|
-
|
|
451
|
+
const dtmf_form_element = msg.attributes?.commands?.find((command) => command.type === SETTING_MESSAGE);
|
|
452
|
+
if (!dtmf_form_element?.settings) {
|
|
453
|
+
return null;
|
|
509
454
|
}
|
|
510
455
|
|
|
456
|
+
const { noInputIntent, noMatchIntent } = dtmf_form_element.settings;
|
|
511
457
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
action: intent_noMatch,
|
|
516
|
-
value: 'no_match'
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
|
|
520
|
-
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
|
|
521
|
-
|
|
522
|
-
/*element.ele("nomatch")
|
|
523
|
-
.ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_MATCH + "'"}).up()
|
|
524
|
-
.ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noMatch) + "'", }).up()
|
|
525
|
-
.ele("goto", { next: "#nomatch_form" }).up()
|
|
526
|
-
.up();
|
|
527
|
-
|
|
528
|
-
rootEle.ele("form", { id: "nomatch_form" })
|
|
529
|
-
.ele("block")
|
|
530
|
-
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_match'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
|
|
531
|
-
.up()
|
|
532
|
-
.up();
|
|
533
|
-
*/
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return { queryNoInput: queryNoInput, queryNoMatch: queryNoMatch}
|
|
539
|
-
|
|
458
|
+
// Build query strings for noInput and noMatch intents
|
|
459
|
+
const queryNoInput = noInputIntent ? `button_action=${noInputIntent.substring(1)}` : '';
|
|
460
|
+
const queryNoMatch = noMatchIntent ? `button_action=${noMatchIntent.substring(1)}` : '';
|
|
540
461
|
|
|
462
|
+
return { queryNoInput, queryNoMatch };
|
|
541
463
|
}
|
|
542
464
|
|
|
543
465
|
/* create PROMPT section */
|
|
544
|
-
async promptVXML(rootEle, msg, attributes) {
|
|
466
|
+
async promptVXML(rootEle, msg, attributes, sessionInfo) {
|
|
545
467
|
//let promt = rootEle.ele("prompt", {bargein: attributes.bargein});
|
|
546
468
|
|
|
547
469
|
if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
|
|
548
470
|
let commands = msg.attributes.commands;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if
|
|
554
|
-
|
|
555
|
-
if(
|
|
556
|
-
let
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
//case type: FRAME
|
|
566
|
-
if(command.message.type === 'frame' && command.message.metadata.src !== ""){
|
|
567
|
-
if(command.message.text != ''){
|
|
568
|
-
rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
|
|
569
|
-
}
|
|
570
|
-
rootEle.ele('Play', {}, command.message.metadata.src )
|
|
471
|
+
|
|
472
|
+
for (const command of commands) {
|
|
473
|
+
if (command.type === "message") {
|
|
474
|
+
//case type: TEXT
|
|
475
|
+
if(command.message.type === 'text'){
|
|
476
|
+
let text = utils.markdownToTwilioSpeech(command.message.text);
|
|
477
|
+
if(this.voiceProvider !== VOICE_PROVIDER.TWILIO){
|
|
478
|
+
let voiceMessageUrl = await this.generateTTS(text, attributes, sessionInfo);
|
|
479
|
+
rootEle.ele('Play', { loop: 0}, voiceMessageUrl );
|
|
480
|
+
this.lastCallSidVerb[attributes.callSid] = "play";
|
|
481
|
+
}else{
|
|
482
|
+
rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
|
|
483
|
+
this.lastCallSidVerb[attributes.callSid] = "say";
|
|
571
484
|
}
|
|
572
|
-
|
|
573
|
-
rootEle.ele("Pause", { length: command.time/1000 }).up();
|
|
485
|
+
|
|
574
486
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
487
|
+
//case type: FRAME
|
|
488
|
+
if(command.message.type === 'frame' && command.message.metadata.src !== ""){
|
|
489
|
+
if(command.message.text != ''){
|
|
490
|
+
let text = utils.markdownToTwilioSpeech(command.message.text);
|
|
491
|
+
rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
|
|
492
|
+
}
|
|
493
|
+
rootEle.ele('Play', { loop: 0 }, command.message.metadata.src )
|
|
494
|
+
this.lastCallSidVerb[attributes.callSid] = "play";
|
|
495
|
+
|
|
581
496
|
}
|
|
497
|
+
} else if (command.type === "wait" && command.time !== 0) {
|
|
498
|
+
rootEle.ele("Pause", { length: command.time/1000 }).up();
|
|
499
|
+
this.lastCallSidVerb[attributes.callSid] = "pause";
|
|
582
500
|
}
|
|
583
|
-
|
|
584
|
-
});
|
|
501
|
+
}
|
|
585
502
|
}
|
|
586
503
|
return rootEle;
|
|
587
504
|
}
|
|
@@ -640,16 +557,16 @@ class TiledeskTwilioTranslator {
|
|
|
640
557
|
return transfer.up()
|
|
641
558
|
}
|
|
642
559
|
|
|
643
|
-
async generateTTS(text, attributes){
|
|
560
|
+
async generateTTS(text, attributes, sessionInfo){
|
|
644
561
|
let audioData = null;
|
|
645
562
|
try {
|
|
646
563
|
switch(this.voiceProvider){
|
|
647
564
|
case VOICE_PROVIDER.OPENAI:
|
|
648
|
-
let GPT_KEY =
|
|
565
|
+
let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
|
|
649
566
|
audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
|
|
650
567
|
break;
|
|
651
568
|
case VOICE_PROVIDER.ELEVENLABS:
|
|
652
|
-
let ELEVENLABS_APIKEY =
|
|
569
|
+
let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
|
|
653
570
|
audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
|
|
654
571
|
break;
|
|
655
572
|
default:
|
|
@@ -660,7 +577,7 @@ class TiledeskTwilioTranslator {
|
|
|
660
577
|
throw new SttError('TTS_FAILED', 'TTS returned no audio data');
|
|
661
578
|
}
|
|
662
579
|
|
|
663
|
-
let fileUrl = await this.uploadService.upload(attributes.callSid, audioData,
|
|
580
|
+
let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, sessionInfo.user)
|
|
664
581
|
winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
|
|
665
582
|
return fileUrl
|
|
666
583
|
} catch (error) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const axios = require("axios").default;
|
|
2
|
+
const logger = require('./logger');
|
|
3
|
+
|
|
4
|
+
class FileUtils {
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async downloadFromUrl(url) {
|
|
8
|
+
try {
|
|
9
|
+
const resbody = await axios({
|
|
10
|
+
url: url,
|
|
11
|
+
responseType: 'arraybuffer',
|
|
12
|
+
method: 'GET'
|
|
13
|
+
});
|
|
14
|
+
const buffer = Buffer.from(resbody.data, 'binary');
|
|
15
|
+
return buffer;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var fileUtils = new FileUtils();
|
|
23
|
+
|
|
24
|
+
module.exports = fileUtils;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
var appRoot = require('app-root-path');
|
|
2
|
+
var winston = require('winston');
|
|
3
|
+
var level = process.env.VOICE_TWILIO_LOG || 'info';
|
|
4
|
+
|
|
5
|
+
var options = {
|
|
6
|
+
file: {
|
|
7
|
+
level:level ,
|
|
8
|
+
filename: `${appRoot}/logs/app.log`,
|
|
9
|
+
handleExceptions: true,
|
|
10
|
+
json: false,
|
|
11
|
+
maxsize: 5242880, // 5MB
|
|
12
|
+
maxFiles: 5,
|
|
13
|
+
colorize: false,
|
|
14
|
+
format: winston.format.simple()
|
|
15
|
+
},
|
|
16
|
+
console: {
|
|
17
|
+
level: level,
|
|
18
|
+
handleExceptions: true,
|
|
19
|
+
json: true,
|
|
20
|
+
colorize: true,
|
|
21
|
+
format: winston.format.simple()
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
var logger = winston.createLogger({
|
|
26
|
+
transports: [
|
|
27
|
+
new winston.transports.Console(options.console)
|
|
28
|
+
],
|
|
29
|
+
exitOnError: false, // do not exit on handled exceptions
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
module.exports = logger;
|
|
@@ -73,27 +73,12 @@ function messageType(msgType, message, user) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function infoMessageType(msg){
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.CHAT_REOPENED){
|
|
83
|
-
return INFO_MESSAGE_TYPE.CHAT_REOPENED
|
|
84
|
-
}
|
|
85
|
-
if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.TOUCHING_OPERATOR){
|
|
86
|
-
return INFO_MESSAGE_TYPE.TOUCHING_OPERATOR
|
|
87
|
-
}
|
|
88
|
-
if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LEAD_UPDATED){
|
|
89
|
-
return INFO_MESSAGE_TYPE.LEAD_UPDATED
|
|
90
|
-
}
|
|
91
|
-
if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP){
|
|
92
|
-
return INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP
|
|
93
|
-
}
|
|
94
|
-
if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LIVE_PAGE){
|
|
95
|
-
return INFO_MESSAGE_TYPE.LIVE_PAGE
|
|
96
|
-
}
|
|
76
|
+
const key = msg?.attributes?.messagelabel?.key;
|
|
77
|
+
if (!key) return null;
|
|
78
|
+
|
|
79
|
+
// Return the key directly if it's a valid INFO_MESSAGE_TYPE
|
|
80
|
+
const validTypes = Object.values(INFO_MESSAGE_TYPE);
|
|
81
|
+
return validTypes.includes(key) ? key : null;
|
|
97
82
|
}
|
|
98
83
|
|
|
99
84
|
module.exports = {isCarousel, isImage, isFile, isAudio, isInfo, isInfoSupport, messageType, infoMessageType}
|