Skip to content

Instantly share code, notes, and snippets.

@chadwallacehart
Last active December 3, 2019 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chadwallacehart/c5071cad01aa670578c2c263c311d83c to your computer and use it in GitHub Desktop.
Save chadwallacehart/c5071cad01aa670578c2c263c311d83c to your computer and use it in GitHub Desktop.
VoxEngine Dialogflow Voicebot example
// 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