Last active
December 3, 2019 15:54
-
-
Save chadwallacehart/c5071cad01aa670578c2c263c311d83c to your computer and use it in GitHub Desktop.
VoxEngine Dialogflow Voicebot example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Voximplant's original Dialogflow integration template here: https://voximplant.com/blog/how-to-use-dialogflow-connector | |
require(Modules.AI) | |
require(Modules.Recorder) | |
const MAX_NO_INPUT_TIME = 10000 // Number of seconds of not hearing anything to wait before prompting the user again | |
const VOICEBOT_PHONE_NUMBER = 18576002939 // CallerID used to place a transfer call | |
let dialogflow, call, hangup = false | |
// Fire the "NO_INPUT" event if there is no speech for MAX_NO_INPUT_TIME | |
class Timer { | |
constructor() { | |
this.expired = false | |
this.noInputTimer = null | |
} | |
start(){ | |
this.noInputTimer = setTimeout(()=>{ | |
this.expired = true | |
Logger.write("No_Input timer exceeded") | |
dialogflow.sendQuery({event : {name: "NO_INPUT", language_code: "en"}}) | |
}, MAX_NO_INPUT_TIME || 15 * 1000) | |
Logger.write("No_Input timer started") | |
} | |
stop(){ | |
this.expired = false | |
clearTimeout(this.noInputTimer) | |
Logger.write("No_Input cleared") | |
} | |
} | |
let timer = new Timer() | |
let waitForTone = false | |
// Inbound call processing | |
VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => { | |
call = e.call | |
call.addEventListener(CallEvents.Connected, onCallConnected) | |
call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate) | |
call.addEventListener(CallEvents.RecordStarted, e => { Logger.write("Recording URL: " + e.url)}) | |
call.handleTones(true) | |
call.addEventListener(CallEvents.ToneReceived, onTone) | |
call.answer() | |
}) | |
function onCallConnected(e) { | |
// Start recording | |
call.record({stereo: true}) | |
// Create Dialogflow object | |
dialogflow = AI.createDialogflow({ | |
lang: DialogflowLanguage.ENGLISH_US | |
}) | |
dialogflow.addEventListener(AI.Events.DialogflowResponse, onDialogflowResponse) | |
// Set a phone context with phone parameters | |
// ToDo: error handling if returned parameters are null? | |
phoneContext = { | |
name: "phone", | |
lifespanCount: 99, | |
parameters: { | |
caller_id: call.callerid(), | |
called_number: call.number() | |
} | |
} | |
dialogflow.setQueryParameters({contexts: [phoneContext]}) | |
// Sending WELCOME event to let the agent says a welcome message | |
dialogflow.sendQuery({event : {name: "WELCOME", language_code: "en"}}) | |
// Playback marker used for better user experience | |
dialogflow.addMarker(-300) | |
// Start sending media from Dialogflow to the call | |
dialogflow.sendMediaTo(call) | |
dialogflow.addEventListener(AI.Events.DialogflowPlaybackFinished, (e) => { | |
// Dialogflow TTS playback finished. Hangup the call if hangup flag was set to true | |
if (hangup) call.hangup() | |
timer.start() | |
waitForTone = false | |
}) | |
dialogflow.addEventListener(AI.Events.DialogflowPlaybackStarted, (e) => { | |
// Dialogflow TTS playback started | |
timer.stop() | |
}) | |
dialogflow.addEventListener(AI.Events.DialogflowPlaybackMarkerReached, (e) => { | |
// Playback marker reached - start sending audio from the call to Dialogflow | |
call.sendMediaTo(dialogflow) | |
}) | |
} | |
// Handle Dialogflow responses | |
function onDialogflowResponse(e) { | |
// If DialogflowResponse with queryResult received - the call stops sending media to Dialogflow | |
// in case of response with queryResult but without responseId we can continue sending media to dialogflow | |
if (e.response.queryResult !== undefined && e.response.responseId === undefined) { | |
if (!timer.expired && !waitForTone) | |
call.sendMediaTo(dialogflow) | |
} else if (e.response.queryResult !== undefined && e.response.responseId !== undefined) { | |
// Do whatever required with e.response.queryResult or e.response.webhookStatus | |
// If we need to hangup because end of conversation has been reached | |
if (e.response.queryResult.diagnosticInfo !== undefined && | |
e.response.queryResult.diagnosticInfo.end_conversation == true) { | |
hangup = true | |
} | |
// Telephony messages arrive in fulfillmentMessages array | |
if (e.response.queryResult.fulfillmentMessages != undefined) { | |
e.response.queryResult.fulfillmentMessages.forEach((msg) => { | |
if (msg.platform !== undefined && msg.platform === "TELEPHONY") processTelephonyMessage(msg) | |
}) | |
} | |
} | |
} | |
// Process telephony messages from Dialogflow | |
function processTelephonyMessage(msg) { | |
// Transfer call to msg.telephonyTransferCall.phoneNumber | |
if (msg.telephonyTransferCall !== undefined) { | |
dialogflow.stop() | |
let newcall = VoxEngine.callPSTN(msg.telephonyTransferCall.phoneNumber, VOICEBOT_PHONE_NUMBER) | |
VoxEngine.easyProcess(call, newcall) | |
} | |
// Synthesize speech from msg.telephonySynthesizeSpeech | |
if (msg.telephonySynthesizeSpeech !== undefined) { | |
// See the list of available TTS languages at https://voximplant.com/docs/references/voxengine/language | |
if(msg.telephonySynthesizeSpeech.ssml !== undefined) | |
call.say(msg.telephonySynthesizeSpeech.ssml, Language.Premium.AU_ENGLISH_MALE) | |
else | |
call.say(msg.telephonySynthesizeSpeech.text, Language.Premium.AU_ENGLISH_MALE) | |
} | |
// Play audio file located at msg.telephonyPlayAudio.audioUri | |
if (msg.telephonyPlayAudio !== undefined) { | |
// audioUri contains Google Storage URI (gs://), we need to transform it to URL (https://) | |
let url = msg.telephonyPlayAudio.audioUri.replace("gs://", "https://storage.cloud.google.com/") | |
call.startPlayback(url) | |
} | |
// ToDo: control needs to be handed back to Dialogflow for telephonySynthesizeSpeech | |
// . and telephonyPlayAudio. Some event handler will be needed to watch for playback completion | |
// ToDo: make sure any playback or synthesized speech has finished playback first | |
if (hangup) call.hangup() | |
} | |
function onTone(e){ | |
// ToDo: error handling - i.e. check to make sure Dialogflow is connected and running first, tone valuess | |
// ToDo: aggregate multiple DTMF keys that are pressed in succession and send as a single string | |
waitForTone = true | |
dialogflow.sendQuery({event : {name: "DTMF", language_code: "en", parameters: { dtmf_digits: e.tone} }}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment