@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
|
@@ -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,22 +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
|
|
310
|
-
|
|
311
|
-
const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
|
|
312
|
-
gather.att("action", this.BASE_URL + '/nextBlock/' + xmlAttributes.callSid + queryUrl)
|
|
313
|
-
// gather.att("action", this.BASE_URL + '/speechresult/' + xmlAttributes.callSid + queryUrl)
|
|
314
|
-
.att("method", "POST")
|
|
315
|
-
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
316
|
-
.att('speechTimeout', "auto")
|
|
317
|
-
.att("enhanced", "true") // enable enhanced recognition
|
|
318
|
-
|
|
319
|
-
const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
|
|
287
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
320
288
|
|
|
321
|
-
|
|
322
|
-
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()
|
|
323
291
|
//prompt.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/nextblock/' + session.connection.calltoken", method: "post", namelist: "usertext session intentName previousIntentTimestamp" });
|
|
324
292
|
|
|
325
293
|
return rootEle.end({ pretty: true });
|
|
@@ -327,7 +295,7 @@ class TiledeskTwilioTranslator {
|
|
|
327
295
|
}
|
|
328
296
|
|
|
329
297
|
/** DONE **/
|
|
330
|
-
async speechFormVXMLConverter(rootEle, message, xmlAttributes) {
|
|
298
|
+
async speechFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
331
299
|
|
|
332
300
|
if(this.voiceProvider === VOICE_PROVIDER.TWILIO){
|
|
333
301
|
const gather = rootEle.ele("Gather", { input: "speech"})
|
|
@@ -347,7 +315,7 @@ class TiledeskTwilioTranslator {
|
|
|
347
315
|
gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
|
|
348
316
|
}
|
|
349
317
|
|
|
350
|
-
const prompt = this.promptVXML(gather, message, xmlAttributes);
|
|
318
|
+
const prompt = this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
351
319
|
|
|
352
320
|
const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
|
|
353
321
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -356,7 +324,7 @@ class TiledeskTwilioTranslator {
|
|
|
356
324
|
|
|
357
325
|
}else{
|
|
358
326
|
|
|
359
|
-
const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
|
|
327
|
+
const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
|
|
360
328
|
|
|
361
329
|
const record = rootEle.ele("Record", { playBeep: "false"})
|
|
362
330
|
|
|
@@ -384,7 +352,7 @@ class TiledeskTwilioTranslator {
|
|
|
384
352
|
}
|
|
385
353
|
|
|
386
354
|
/** DONE **/
|
|
387
|
-
async menuVXMLConverter(rootEle, message, xmlAttributes) {
|
|
355
|
+
async menuVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
388
356
|
const lastMessageCommand = message.attributes.commands.slice(-3)[0];
|
|
389
357
|
const options = this.getButtonsFromCommand(lastMessageCommand);
|
|
390
358
|
|
|
@@ -407,7 +375,7 @@ class TiledeskTwilioTranslator {
|
|
|
407
375
|
.att("method", "POST")
|
|
408
376
|
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
409
377
|
|
|
410
|
-
const prompt = await this.promptVXML(gather, message, xmlAttributes);
|
|
378
|
+
const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
411
379
|
|
|
412
380
|
|
|
413
381
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -418,7 +386,7 @@ class TiledeskTwilioTranslator {
|
|
|
418
386
|
}
|
|
419
387
|
|
|
420
388
|
/** DONE **/
|
|
421
|
-
async dtmfFormVXMLConverter(rootEle, message, xmlAttributes) {
|
|
389
|
+
async dtmfFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
422
390
|
|
|
423
391
|
const gather = rootEle.ele("Gather", { input: "dtmf"})
|
|
424
392
|
|
|
@@ -429,7 +397,7 @@ class TiledeskTwilioTranslator {
|
|
|
429
397
|
.att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
|
|
430
398
|
const settings = await this.optionsVXML(gather, message, xmlAttributes);
|
|
431
399
|
|
|
432
|
-
const prompt = this.promptVXML(gather, message, xmlAttributes);
|
|
400
|
+
const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
|
|
433
401
|
|
|
434
402
|
const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
|
|
435
403
|
if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
|
|
@@ -441,12 +409,12 @@ class TiledeskTwilioTranslator {
|
|
|
441
409
|
}
|
|
442
410
|
|
|
443
411
|
|
|
444
|
-
async blindTransferVXMLConverter(rootEle, message, xmlAttributes) {
|
|
412
|
+
async blindTransferVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
|
|
445
413
|
const lastMessageCommand = message.attributes.commands.slice(-3)[0];
|
|
446
414
|
const options = this.getButtonsFromCommand(lastMessageCommand);
|
|
447
415
|
|
|
448
|
-
const prompt = this.promptVXML(rootEle, message, xmlAttributes);
|
|
449
|
-
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)
|
|
450
418
|
|
|
451
419
|
|
|
452
420
|
return rootEle.end({ pretty: true });
|
|
@@ -474,124 +442,63 @@ class TiledeskTwilioTranslator {
|
|
|
474
442
|
rootEle.ele("catch", { event: "connection.disconnect.hangup" })
|
|
475
443
|
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/hangup'", method: "post", namelist: "intentName previousIntentTimestamp"}).up();
|
|
476
444
|
rootEle.ele("catch", { event: "error.noresource.asr" })
|
|
477
|
-
//.ele("var", { name: "url", expr: "'https://tiledesk-vxml-connector.glitch.me/event" + "/no-resources?callId=" + attributes.callId + "'" }).up()
|
|
478
445
|
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/no-resources?event=' + application._event", method: "post", namelist: "intentName previousIntentTimestamp" }).up();
|
|
479
446
|
return rootEle;
|
|
480
447
|
}
|
|
481
448
|
|
|
482
449
|
|
|
483
450
|
async handleNoInputNoMatch(rootEle, msg, attributes){
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
let dtmf_form_element = msg.attributes.commands.find((command) => command.type === SETTING_MESSAGE)
|
|
489
|
-
if(!dtmf_form_element){
|
|
490
|
-
return;
|
|
451
|
+
const dtmf_form_element = msg.attributes?.commands?.find((command) => command.type === SETTING_MESSAGE);
|
|
452
|
+
if (!dtmf_form_element?.settings) {
|
|
453
|
+
return null;
|
|
491
454
|
}
|
|
492
455
|
|
|
456
|
+
const { noInputIntent, noMatchIntent } = dtmf_form_element.settings;
|
|
493
457
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
action: intent_noInput,
|
|
498
|
-
value: 'no_input'
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
queryNoInput = 'button_action='+button_noIput.action.substring(1);
|
|
503
|
-
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
/*element.ele("noinput")
|
|
507
|
-
.ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_INPUT + "'", }).up()
|
|
508
|
-
.ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noIput) + "'", }).up()
|
|
509
|
-
.ele("goto", { next: "#noinput_form" }).up()
|
|
510
|
-
.up()
|
|
511
|
-
|
|
512
|
-
rootEle.ele("form", { id: "noinput_form" })
|
|
513
|
-
.ele("block")
|
|
514
|
-
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_input'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
|
|
515
|
-
.up()
|
|
516
|
-
.up();
|
|
517
|
-
*/
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if(dtmf_form_element.settings.noMatchIntent){
|
|
523
|
-
intent_noMatch = dtmf_form_element.settings.noMatchIntent
|
|
524
|
-
button_noMatch = {
|
|
525
|
-
action: intent_noMatch,
|
|
526
|
-
value: 'no_match'
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
|
|
530
|
-
//rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
|
|
531
|
-
|
|
532
|
-
/*element.ele("nomatch")
|
|
533
|
-
.ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_MATCH + "'"}).up()
|
|
534
|
-
.ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noMatch) + "'", }).up()
|
|
535
|
-
.ele("goto", { next: "#nomatch_form" }).up()
|
|
536
|
-
.up();
|
|
537
|
-
|
|
538
|
-
rootEle.ele("form", { id: "nomatch_form" })
|
|
539
|
-
.ele("block")
|
|
540
|
-
.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_match'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
|
|
541
|
-
.up()
|
|
542
|
-
.up();
|
|
543
|
-
*/
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return { queryNoInput: queryNoInput, queryNoMatch: queryNoMatch}
|
|
549
|
-
|
|
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)}` : '';
|
|
550
461
|
|
|
462
|
+
return { queryNoInput, queryNoMatch };
|
|
551
463
|
}
|
|
552
464
|
|
|
553
465
|
/* create PROMPT section */
|
|
554
|
-
async promptVXML(rootEle, msg, attributes) {
|
|
466
|
+
async promptVXML(rootEle, msg, attributes, sessionInfo) {
|
|
555
467
|
//let promt = rootEle.ele("prompt", {bargein: attributes.bargein});
|
|
556
468
|
|
|
557
469
|
if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
|
|
558
470
|
let commands = msg.attributes.commands;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if
|
|
564
|
-
|
|
565
|
-
if(
|
|
566
|
-
let
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
//case type: FRAME
|
|
576
|
-
if(command.message.type === 'frame' && command.message.metadata.src !== ""){
|
|
577
|
-
if(command.message.text != ''){
|
|
578
|
-
rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
|
|
579
|
-
}
|
|
580
|
-
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";
|
|
581
484
|
}
|
|
582
|
-
|
|
583
|
-
rootEle.ele("Pause", { length: command.time/1000 }).up();
|
|
485
|
+
|
|
584
486
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
+
|
|
591
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";
|
|
592
500
|
}
|
|
593
|
-
|
|
594
|
-
});
|
|
501
|
+
}
|
|
595
502
|
}
|
|
596
503
|
return rootEle;
|
|
597
504
|
}
|
|
@@ -650,16 +557,16 @@ class TiledeskTwilioTranslator {
|
|
|
650
557
|
return transfer.up()
|
|
651
558
|
}
|
|
652
559
|
|
|
653
|
-
async generateTTS(text, attributes){
|
|
560
|
+
async generateTTS(text, attributes, sessionInfo){
|
|
654
561
|
let audioData = null;
|
|
655
562
|
try {
|
|
656
563
|
switch(this.voiceProvider){
|
|
657
564
|
case VOICE_PROVIDER.OPENAI:
|
|
658
|
-
let GPT_KEY =
|
|
565
|
+
let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
|
|
659
566
|
audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
|
|
660
567
|
break;
|
|
661
568
|
case VOICE_PROVIDER.ELEVENLABS:
|
|
662
|
-
let ELEVENLABS_APIKEY =
|
|
569
|
+
let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
|
|
663
570
|
audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
|
|
664
571
|
break;
|
|
665
572
|
default:
|
|
@@ -670,7 +577,7 @@ class TiledeskTwilioTranslator {
|
|
|
670
577
|
throw new SttError('TTS_FAILED', 'TTS returned no audio data');
|
|
671
578
|
}
|
|
672
579
|
|
|
673
|
-
let fileUrl = await this.uploadService.upload(attributes.callSid, audioData,
|
|
580
|
+
let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, sessionInfo.user)
|
|
674
581
|
winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
|
|
675
582
|
return fileUrl
|
|
676
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}
|