diff --git a/lib/session/call-session.js b/lib/session/call-session.js index f33efff1..13e285e2 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -202,7 +202,6 @@ class CallSession extends Emitter { */ getSpeechCredentials(vendor, type) { const {writeAlerts, AlertType} = this.srf.locals; - this.logger.debug({vendor, type, speech: this.accountInfo.speech}, `searching for speech for vendor ${vendor}`); if (this.accountInfo.speech && this.accountInfo.speech.length > 0) { const credential = this.accountInfo.speech.find((s) => s.vendor === vendor); if (credential && ( diff --git a/lib/tasks/gather.js b/lib/tasks/gather.js index 188a94bf..30f401d1 100644 --- a/lib/tasks/gather.js +++ b/lib/tasks/gather.js @@ -10,7 +10,7 @@ const makeTask = require('./make_task'); const assert = require('assert'); class TaskGather extends Task { - constructor(logger, opts) { + constructor(logger, opts, parentTask) { super(logger, opts); this.preconditions = TaskPreconditions.Endpoint; @@ -40,6 +40,8 @@ class TaskGather extends Task { if (this.say) this.sayTask = makeTask(this.logger, {say: this.say}, this); if (this.play) this.playTask = makeTask(this.logger, {play: this.play}, this); + + this.parentTask = parentTask; } get name() { return TaskName.Gather; } @@ -154,7 +156,6 @@ class TaskGather extends Task { }); ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep)); } - this.logger.debug({vars: opts}, 'setting freeswitch vars'); await ep.set(opts) .catch((err) => this.logger.info(err, 'Error setting channel variables')); @@ -235,7 +236,11 @@ class TaskGather extends Task { await this.performAction({digits: this.digitBuffer}); } else if (reason.startsWith('speech')) { - await this.performAction({speech: evt}); + if (this.parentTask) this.parentTask.emit('transcription', evt); + else await this.performAction({speech: evt}); + } + else if (reason.startsWith('timeout') && this.parentTask) { + this.parentTask.emit('timeout', evt); } this.notifyTaskDone(); } diff --git a/lib/tasks/make_task.js b/lib/tasks/make_task.js index 089fb389..0803d4fd 100644 --- a/lib/tasks/make_task.js +++ b/lib/tasks/make_task.js @@ -48,6 +48,9 @@ function makeTask(logger, obj, parent) { case TaskName.Message: const TaskMessage = require('./message'); return new TaskMessage(logger, data, parent); + case TaskName.Rasa: + const TaskRasa = require('./rasa'); + return new TaskRasa(logger, data, parent); case TaskName.Say: const TaskSay = require('./say'); return new TaskSay(logger, data, parent); diff --git a/lib/tasks/rasa.js b/lib/tasks/rasa.js new file mode 100644 index 00000000..aeb916c1 --- /dev/null +++ b/lib/tasks/rasa.js @@ -0,0 +1,109 @@ +const Task = require('./task'); +const {TaskName, TaskPreconditions} = require('../utils/constants'); +const makeTask = require('./make_task'); +const bent = require('bent'); + +class Rasa extends Task { + constructor(logger, opts) { + super(logger, opts); + this.preconditions = TaskPreconditions.Endpoint; + + this.prompt = this.data.prompt; + this.eventHook = this.data?.eventHook; + this.actionHook = this.data?.actionHook; + this.post = bent('POST', 'json', 200); + } + + get name() { return TaskName.Rasa; } + + async exec(cs, ep) { + await super.exec(cs); + + this.ep = ep; + try { + /* set event handlers */ + this.on('transcription', this._onTranscription.bind(this, cs, ep)); + this.on('timeout', this._onTimeout.bind(this, cs, ep)); + + /* start the first gather */ + this.gatherTask = this._makeGatherTask(this.prompt); + this.gatherTask.exec(cs, ep, this) + .catch((err) => this.logger.info({err}, 'Rasa gather task returned error')); + + await this.awaitTaskDone(); + } catch (err) { + this.logger.error({err}, 'Rasa error'); + throw err; + } + } + + async kill(cs) { + super.kill(cs); + if (this.ep.connected) { + this.logger.debug('Rasa:kill'); + + this.performAction({rasaResult: 'caller hungup'}) + .catch((err) => this.logger.error({err}, 'rasa - error w/ action webook')); + + await this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio')); + } + this.removeAllListeners(); + this.notifyTaskDone(); + } + + _makeGatherTask(prompt) { + let opts = { + input: ['speech'], + timeout: this.data.timeout || 10, + recognizer: this.data.recognizer || { + vendor: 'default', + language: 'default' + } + }; + if (prompt) { + const sayOpts = this.data.tts ? + {text: prompt, synthesizer: this.data.tts} : + {text: prompt}; + + opts = { + ...opts, + say: sayOpts + }; + } + this.logger.debug({opts}, 'constructing a nested gather object'); + const gather = makeTask(this.logger, {gather: opts}, this); + return gather; + } + + async _onTranscription(cs, ep, evt) { + this.logger.debug({evt}, `Rasa: got transcription for callSid ${cs.callSid}`); + const utterance = evt.alternatives[0].transcript; + try { + const payload = { + sender: cs.callSid, + message: utterance + }; + this.logger.debug({payload}, 'Rasa:_onTranscription - sending payload to Rasa'); + const response = await this.post(this.data.url, payload); + this.logger.debug({response}, 'Rasa:_onTranscription - got response from Rasa'); + const botUtterance = Array.isArray(response) ? + response.reduce((prev, current) => `${prev} ${current.text}`, '') : + null; + if (botUtterance) { + this.logger.debug({botUtterance}, 'playing out bot utterance'); + this.gatherTask = this._makeGatherTask(botUtterance); + this.gatherTask.exec(cs, ep, this) + .catch((err) => this.logger.info({err}, 'Rasa gather task returned error')); + } + } catch (err) { + this.logger.error({err}, 'Error sending'); + } + } + _onTimeout(cs, ep, evt) { + this.logger.debug({evt}, 'Rasa: got timeout'); + } + + +} + +module.exports = Rasa; diff --git a/lib/tasks/specs.json b/lib/tasks/specs.json index 1de348fe..eb164735 100644 --- a/lib/tasks/specs.json +++ b/lib/tasks/specs.json @@ -78,7 +78,6 @@ "say": "#say" }, "required": [ - "actionHook" ] }, "conference": { @@ -228,6 +227,19 @@ "length" ] }, + "rasa": { + "properties": { + "url": "string", + "recognizer": "#recognizer", + "tts": "#synthesizer", + "prompt": "string", + "actionHook": "object|string", + "eventHook": "object|string" + }, + "required": [ + "url" + ] + }, "redirect": { "properties": { "actionHook": "object|string" diff --git a/lib/utils/constants.json b/lib/utils/constants.json index 4961d9ea..652a5d13 100644 --- a/lib/utils/constants.json +++ b/lib/utils/constants.json @@ -14,6 +14,7 @@ "Message": "message", "Pause": "pause", "Play": "play", + "Rasa": "rasa", "Redirect": "redirect", "RestDial": "rest:dial", "SipDecline": "sip:decline",