diff --git a/lib/tasks/cognigy/index.js b/lib/tasks/cognigy/index.js index bbe0bffb..50b5029a 100644 --- a/lib/tasks/cognigy/index.js +++ b/lib/tasks/cognigy/index.js @@ -73,6 +73,7 @@ class Cognigy extends Task { resolver = resolve; rejector = reject; }); + taskPromise.resolve = resolver; this.taskQueue.push(async(cb) => { this.logger.debug('executing task from queue'); try { @@ -86,6 +87,10 @@ class Cognigy extends Task { } this.logger.debug('say task executed from queue'); }); + if(this.taskQueue.lastPromise){ + // resolve the previous promise for cleanup + this.taskQueue.lastPromise.resolve({}); + } this.taskQueue.lastPromise = taskPromise; return taskPromise; } @@ -180,6 +185,19 @@ class Cognigy extends Task { this.notifyTaskDone(); } + /** + * Creates a promt which will be sent to the consumer. We will create a say task if bargein is disabled + * for session and nextTurn, else create a gather task. + */ + _createPromtTask({text, url, turnConfig, dontListenAfterSpeech} = {}){ + const bargeInOnNextTurn = turnConfig?.bargein?.enable?.length>0; + const bargeInSession = this.config.bargeInEnabled; + if(bargeInOnNextTurn || bargeInSession){ + return this._makeGatherTask({textPrompt: text, url: urlPrompt, turnConfig, dontListenAfterSpeech}); + } + return this._makeSayTask({text, turnConfig}); + } + _makeGatherTask({textPrompt, urlPrompt, turnConfig} = {}) { this.logger.debug({textPrompt, urlPrompt, turnConfig}, '_makeGatherTask'); const config = this.config.makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig}); @@ -274,11 +292,11 @@ class Cognigy extends Task { const text = parseBotText(evt); // only add say task if its a normal cognigy node and not a "gather task" - if (text && (evt?.data?.type !== 'gather')) { + if (text && (evt?.data?.type !== 'promt')) { this.logger.info({text}, 'received text'); this._enqueueTask(async() => { // todo inject the session config into the say task - const sayTask = this._makeSayTask({text}); + const sayTask = this._createPromtTask({ text, dontListenAfterSpeech: true }); await sayTask.exec(cs, ep, this); this.logger.debug({text}, 'executed say task'); }); @@ -304,9 +322,9 @@ class Cognigy extends Task { return; case 'promt': this._enqueueTask(async() => { - const sayTask = this._makeSayTask({ + const sayTask = this._createPromtTask({ text: evt.data.text, - nextTurn: evt?.data?.config?.nextTurn + turnConfig: evt?.data?.config?.nextTurn }); try { await sayTask.exec(cs, ep, this); @@ -337,6 +355,17 @@ class Cognigy extends Task { this.logger.debug({evt}, `Cognigy: got transcription for callSid ${cs.callSid}`); const utterance = evt.alternatives[0].transcript; + //if we have barge in enabled AND we enabled skipping until next question + //then stop execution of currently queues bot output before sending the + //response to waiting bot since otherwise we could stop upcoming bot output + + if(this.config.skipUntilBotInput){ + // clear task queue, resolve the last promise and cleanup; + this.taskQueue.end(); + this.taskQueue.lastPromise.resolve(); + this.taskQueue.autostart = true; + } + if (this.eventHook) { this.performHook(cs, this.eventHook, {event: 'userMessage', message: utterance}) .then((redirected) => { @@ -376,7 +405,7 @@ class Cognigy extends Task { /* send dtmf to bot */ try { if (this.client && this.client.connected) { - this.client.sendMessage(evt.digits); + this.client.sendMessage(String(evt.digits)); } else { // if the bot is not connected, should we maybe throw an error here? diff --git a/lib/tasks/cognigy/speech-config.js b/lib/tasks/cognigy/speech-config.js index b0a48bb4..68911c53 100644 --- a/lib/tasks/cognigy/speech-config.js +++ b/lib/tasks/cognigy/speech-config.js @@ -28,6 +28,19 @@ class SpeechConfig extends Emitter { this.logger.debug({sessionLevel: this.sessionConfig}, 'SpeechConfig updated'); } + /** + * check if we should skip all nodes until next bot input + */ + get skipUntilBotInput(){ + return this.sessionConfig.bargein?.skipUntilBotInput === false; + } + /** + * Check if barge is enabled on session level + */ + get bargeInEnabled(){ + return this.sessionConfig.bargein?.enable?.length > 0; + } + makeSayTaskConfig({text, turnConfig = {}} = {}) { const synthesizer = lodash.merge({}, this.sessionConfig.synthesizer, turnConfig.synthesizer); return { @@ -36,7 +49,7 @@ class SpeechConfig extends Emitter { }; } - makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig = {}} = {}) { + makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig = {}, dontListenAfterSpeech} = {}) { // we merge from top to bottom deeply so we wil have // defaults from session config and then will override them via turn config const opts = lodash.merge( @@ -112,7 +125,8 @@ class SpeechConfig extends Emitter { noInputRetries, noInputSpeech, noInputUrl - } + }, + dontListenAfterSpeech }; const final = stripNulls(config); diff --git a/lib/tasks/gather.js b/lib/tasks/gather.js index 1803cb01..4d243913 100644 --- a/lib/tasks/gather.js +++ b/lib/tasks/gather.js @@ -23,6 +23,9 @@ class TaskGather extends Task { ].forEach((k) => this[k] = this.data[k]); this.listenDuringPrompt = this.data.listenDuringPrompt === false ? false : true; this.minBargeinWordCount = this.data.minBargeinWordCount || 1; + // this is specially for barge in where we want to make a bargebale promt + // to a user without listening after the say task has finished + this.dontListenAfterSpeech = this.data.dontListenAfterSpeech === true; this.timeout = (this.timeout || 15) * 1000; this.interim = this.partialResultCallback || this.bargein; if (this.data.recognizer) { @@ -101,14 +104,14 @@ class TaskGather extends Task { this.sayTask.on('playDone', async(err) => { if (err) return this.logger.error({err}, 'Gather:exec Error playing tts'); this.logger.debug('Gather: say task completed'); - if (!this.killed) startListening(cs, ep); + if (!this.killed || !this.dontListenAfterSpeech) startListening(cs, ep); }); } else if (this.playTask) { this.playTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete this.playTask.on('playDone', async(err) => { if (err) return this.logger.error({err}, 'Gather:exec Error playing url'); - if (!this.killed) startListening(cs, ep); + if (!this.killed || !this.dontListenAfterSpeech) startListening(cs, ep); }); } else startListening(cs, ep); diff --git a/lib/tasks/say.js b/lib/tasks/say.js index fc3a5fe8..b2999c0f 100644 --- a/lib/tasks/say.js +++ b/lib/tasks/say.js @@ -22,11 +22,9 @@ class TaskSay extends Task { const {writeAlerts, AlertType, stats} = srf.locals; const {synthAudio} = srf.locals.dbHelpers; - // todo maybe we should allow the user to only change the voice/language without changing the vendor? - const hasVerbLevelTts = this.synthesizer.vendor && this.synthesizer.vendor !== 'default'; - const vendor = hasVerbLevelTts ? this.synthesizer.vendor : cs.speechSynthesisVendor ; - const language = hasVerbLevelTts ? this.synthesizer.language : cs.speechSynthesisLanguage ; - const voice = hasVerbLevelTts ? this.synthesizer.voice : cs.speechSynthesisVoice ; + const vendor = this.synthesizer.vendor || cs.speechSynthesisVendor; + const language = this.synthesizer.language || cs.speechSynthesisLanguage ; + const voice = this.synthesizer.voice || cs.speechSynthesisVoice ; const engine = this.synthesizer.engine || 'standard'; const salt = cs.callSid; const credentials = cs.getSpeechCredentials(vendor, 'tts');