Last active
May 19, 2020 13:59
-
-
Save luddilo/772c30c5fb607806e6680445b6557105 to your computer and use it in GitHub Desktop.
A voximplant "scenario" supporting both Incomign and Outgoing calls with a Dialogflow agent built with Narratory
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
require(Modules.AI) | |
var debug = true | |
// Agent settings | |
var agentId = YOUR_DIALOGFLOW_AGENT_ID_IN_VOXIMPLANT // Should be a number, for example 2600. Check the Dialogflow Connector menu in Voximplant | |
var CALLER_ID = "YOUR_REAL_VOXIMPLANT_PHONE_NUMBER" // Cannot be a virtual number. Format should be for example 46701231212 for +46701231212 | |
var lang = DialogflowLanguage.SWEDISH | |
// Other variables | |
var dialogflow, call, hangup | |
var singleResponse = false | |
var shortAnswers = [] | |
// Inbound call processing - remove processing by commenting the code | |
VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => { | |
log("Incoming call received") | |
call = e.call | |
call.addEventListener(CallEvents.Connected, onCallConnected) | |
call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate) | |
call.answer() | |
}) | |
function makeOutgoingCall(number) { | |
call = VoxEngine.callPSTN(number, CALLER_ID) // replace CALLER_ID with the number we bought in previous step (the real one, test number won't work) | |
call.addEventListener(CallEvents.Connected, onCallConnected) | |
call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate) | |
call.addEventListener(CallEvents.Failed, VoxEngine.terminate) | |
} | |
function log(message, force) { | |
if (debug | force) { | |
Logger.write("#### " + message) | |
} | |
} | |
function getInputData() { | |
var data = {} | |
try { | |
data = JSON.parse(VoxEngine.customData()) | |
log("Custom-data received: " + JSON.stringify(data)) | |
} catch (e) { | |
log( | |
"Could not parse custom-data, so this is likely an incoming call. Message: " + | |
e.message, | |
true | |
) | |
return {} | |
} | |
return data | |
} | |
// Create outbound call as soon as StartScenarios HTTP API arrives | |
VoxEngine.addEventListener(AppEvents.Started, (e) => { | |
log("Got scenario start event") | |
let number = getInputData().number // We try to fetch the number, if we get one, we try to call the number. If not, it is likely an incoming call | |
if (number) { | |
log("Number received in payload, making call to " + number) | |
makeOutgoingCall(number) | |
} | |
}) | |
function onCallConnected(e) { | |
// Get metadata from our input | |
let { sessionId, name } = getInputData() | |
// Create Dialogflow object | |
log("Initializing Dialogflow client") | |
dialogflow = AI.createDialogflow({ | |
agentId: agentId, | |
lang: lang, | |
queryParameters: { | |
payload: { | |
platform: "voximplant", | |
}, | |
}, | |
singleUtterance: false, | |
sessionId: sessionId, | |
}) | |
// Handler for when we get a response from Dialogflow | |
dialogflow.addEventListener( | |
AI.Events.DialogflowResponse, | |
onDialogflowResponse | |
) | |
// Sending WELCOME event to let the agent says a welcome message | |
log("Sending welcome message to Dialogflow") | |
dialogflow.sendQuery({ event: { name: "WELCOME", language_code: lang } }) | |
// Playback marker used for better user experience | |
dialogflow.addMarker(-300) | |
// Start sending media from Dialogflow to the call | |
log("Starting streaming media from Dialogflow to call") | |
dialogflow.sendMediaTo(call) | |
dialogflow.addEventListener(AI.Events.DialogflowPlaybackStarted, (e) => { | |
// Dialogflow TTS playback started | |
log("Audio playback started") | |
}) | |
dialogflow.addEventListener( | |
AI.Events.DialogflowPlaybackMarkerReached, | |
(e) => { | |
// Playback marker reached - start sending audio from the call to Dialogflow | |
log("Marker reached, so starting to send audio to Dialogflow") | |
call.sendMediaTo(dialogflow) | |
} | |
) | |
dialogflow.addEventListener(AI.Events.DialogflowPlaybackFinished, (e) => { | |
// Dialogflow TTS playback finished. Hangup the call if hangup flag was set to true | |
log("Audio playback finished") | |
if (hangup) { | |
call.hangup() | |
log("Hanging up") | |
} | |
}) | |
} | |
// Handle Dialogflow responses | |
function onDialogflowResponse(e) { | |
// If we have an intermediary recognition result from Dialogflow | |
if (e.response.queryResult) { | |
let webhookPayload = e.response.queryResult.webhookPayload | |
if ( | |
webhookPayload && | |
webhookPayload.expectShortAnswer && | |
webhookPayload.shortAnswers && | |
webhookPayload.shortAnswers.length > 0 | |
) { | |
log( | |
"Expecting a short answer, so starting check for intermediary speech recognition results" | |
) | |
singleResponse = true | |
shortAnswers = webhookPayload.shortAnswers | |
} else { | |
singleResponse = false | |
shortAnswers = [] | |
} | |
} | |
if (e.response.recognitionResult && !e.response.queryResult) { | |
const transcript = e.response.recognitionResult.transcript | |
log("Caught an intermediery speech response") | |
if (transcript) { | |
if (e.response.recognitionResult.isFinal) { | |
log("Got a final response from Dialogflow so sending query") | |
call.stopMediaTo(dialogflow) // Stop sending media to Dialogflow | |
} else if (singleResponse) { | |
log("Checking for single-response match of intermediary response") | |
if (shortAnswers.includes(transcript.toLowerCase())) { | |
log( | |
"Intermediery speech response that matched one of the short-answers, sending query directly to DF" | |
) | |
singleResponse = false // Reset settings for shortAnswer | |
call.stopMediaTo(dialogflow) // Stop sending media to Dialogflow | |
} else { | |
log( | |
"Intermediery speech response didn't match single-response, continuing" | |
) | |
} | |
} | |
} else { | |
log("Intermediary response had no transcript, so doing nothing") | |
} | |
} else if (e.response.queryResult && e.response.responseId) { | |
// We get a final response from Dialogflow | |
handleReply(e) | |
} else { | |
// In case of response with queryResult but without responseId we can continue sending media to dialogflow | |
log("No responseId yet so continuing sending media to Dialogflow") | |
call.sendMediaTo(dialogflow) | |
} | |
} | |
function handleReply(e) { | |
if ( | |
e.response.queryResult.webhookPayload && | |
e.response.queryResult.webhookPayload.endOfConversation | |
) { | |
hangup = true | |
} | |
// Telephony messages arrive in fulfillmentMessages array | |
if (e.response.queryResult.fulfillmentMessages != undefined) { | |
log("Got an answer from Dialogflow") | |
} | |
} | |
// Process telephony messages from Dialogflow | |
function processTelephonyMessage(msg) { | |
log( | |
"Processing message, not really doing anything. " + | |
JSON.stringify(msg, null, 2) | |
) | |
// Transfer call to msg.telephonyTransferCall.phoneNumber | |
if (msg.telephonyTransferCall !== undefined) { | |
/** | |
* Example: | |
* dialogflow.stop() | |
* let newcall = VoxEngine.callPSTN(msg.telephonyTransferCall.phoneNumber, "put verified CALLER_ID here") | |
* VoxEngine.easyProcess(call, newcall) | |
*/ | |
} | |
// Synthesize speech from msg.telephonySynthesizeSpeech.text | |
if (msg.telephonySynthesizeSpeech !== undefined) { | |
// See the list of available TTS languages at https://voximplant.com/docs/references/voxengine/language | |
// Example: | |
// if (msg.telephonySynthesizeSpeech.ssml !== undefined) call.say(msg.telephonySynthesizeSpeech.ssml, {"language": VoiceList.Amazon.en_US_Joanna}) | |
// else call.say(msg.telephonySynthesizeSpeech.text, {"language": VoiceList.Amazon.en_US_Joanna}) | |
} | |
// 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.googleapis.com/" | |
) | |
// Example: call.startPlayback(url) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment