drop queue speech on barge in

This commit is contained in:
akirilyuk
2022-02-03 17:47:11 +01:00
parent a1c59a5f25
commit 7e9b6498c5
4 changed files with 58 additions and 14 deletions

View File

@@ -73,6 +73,7 @@ class Cognigy extends Task {
resolver = resolve; resolver = resolve;
rejector = reject; rejector = reject;
}); });
taskPromise.resolve = resolver;
this.taskQueue.push(async(cb) => { this.taskQueue.push(async(cb) => {
this.logger.debug('executing task from queue'); this.logger.debug('executing task from queue');
try { try {
@@ -86,6 +87,10 @@ class Cognigy extends Task {
} }
this.logger.debug('say task executed from queue'); 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; this.taskQueue.lastPromise = taskPromise;
return taskPromise; return taskPromise;
} }
@@ -180,6 +185,19 @@ class Cognigy extends Task {
this.notifyTaskDone(); 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} = {}) { _makeGatherTask({textPrompt, urlPrompt, turnConfig} = {}) {
this.logger.debug({textPrompt, urlPrompt, turnConfig}, '_makeGatherTask'); this.logger.debug({textPrompt, urlPrompt, turnConfig}, '_makeGatherTask');
const config = this.config.makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig}); const config = this.config.makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig});
@@ -274,11 +292,11 @@ class Cognigy extends Task {
const text = parseBotText(evt); const text = parseBotText(evt);
// only add say task if its a normal cognigy node and not a "gather task" // 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.logger.info({text}, 'received text');
this._enqueueTask(async() => { this._enqueueTask(async() => {
// todo inject the session config into the say task // 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); await sayTask.exec(cs, ep, this);
this.logger.debug({text}, 'executed say task'); this.logger.debug({text}, 'executed say task');
}); });
@@ -304,9 +322,9 @@ class Cognigy extends Task {
return; return;
case 'promt': case 'promt':
this._enqueueTask(async() => { this._enqueueTask(async() => {
const sayTask = this._makeSayTask({ const sayTask = this._createPromtTask({
text: evt.data.text, text: evt.data.text,
nextTurn: evt?.data?.config?.nextTurn turnConfig: evt?.data?.config?.nextTurn
}); });
try { try {
await sayTask.exec(cs, ep, this); 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}`); this.logger.debug({evt}, `Cognigy: got transcription for callSid ${cs.callSid}`);
const utterance = evt.alternatives[0].transcript; 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) { if (this.eventHook) {
this.performHook(cs, this.eventHook, {event: 'userMessage', message: utterance}) this.performHook(cs, this.eventHook, {event: 'userMessage', message: utterance})
.then((redirected) => { .then((redirected) => {
@@ -376,7 +405,7 @@ class Cognigy extends Task {
/* send dtmf to bot */ /* send dtmf to bot */
try { try {
if (this.client && this.client.connected) { if (this.client && this.client.connected) {
this.client.sendMessage(evt.digits); this.client.sendMessage(String(evt.digits));
} }
else { else {
// if the bot is not connected, should we maybe throw an error here? // if the bot is not connected, should we maybe throw an error here?

View File

@@ -28,6 +28,19 @@ class SpeechConfig extends Emitter {
this.logger.debug({sessionLevel: this.sessionConfig}, 'SpeechConfig updated'); 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 = {}} = {}) { makeSayTaskConfig({text, turnConfig = {}} = {}) {
const synthesizer = lodash.merge({}, this.sessionConfig.synthesizer, turnConfig.synthesizer); const synthesizer = lodash.merge({}, this.sessionConfig.synthesizer, turnConfig.synthesizer);
return { 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 // we merge from top to bottom deeply so we wil have
// defaults from session config and then will override them via turn config // defaults from session config and then will override them via turn config
const opts = lodash.merge( const opts = lodash.merge(
@@ -112,7 +125,8 @@ class SpeechConfig extends Emitter {
noInputRetries, noInputRetries,
noInputSpeech, noInputSpeech,
noInputUrl noInputUrl
} },
dontListenAfterSpeech
}; };
const final = stripNulls(config); const final = stripNulls(config);

View File

@@ -23,6 +23,9 @@ class TaskGather extends Task {
].forEach((k) => this[k] = this.data[k]); ].forEach((k) => this[k] = this.data[k]);
this.listenDuringPrompt = this.data.listenDuringPrompt === false ? false : true; this.listenDuringPrompt = this.data.listenDuringPrompt === false ? false : true;
this.minBargeinWordCount = this.data.minBargeinWordCount || 1; 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.timeout = (this.timeout || 15) * 1000;
this.interim = this.partialResultCallback || this.bargein; this.interim = this.partialResultCallback || this.bargein;
if (this.data.recognizer) { if (this.data.recognizer) {
@@ -101,14 +104,14 @@ class TaskGather extends Task {
this.sayTask.on('playDone', async(err) => { this.sayTask.on('playDone', async(err) => {
if (err) return this.logger.error({err}, 'Gather:exec Error playing tts'); if (err) return this.logger.error({err}, 'Gather:exec Error playing tts');
this.logger.debug('Gather: say task completed'); 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) { else if (this.playTask) {
this.playTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete this.playTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete
this.playTask.on('playDone', async(err) => { this.playTask.on('playDone', async(err) => {
if (err) return this.logger.error({err}, 'Gather:exec Error playing url'); 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); else startListening(cs, ep);

View File

@@ -22,11 +22,9 @@ class TaskSay extends Task {
const {writeAlerts, AlertType, stats} = srf.locals; const {writeAlerts, AlertType, stats} = srf.locals;
const {synthAudio} = srf.locals.dbHelpers; const {synthAudio} = srf.locals.dbHelpers;
// todo maybe we should allow the user to only change the voice/language without changing the vendor? const vendor = this.synthesizer.vendor || cs.speechSynthesisVendor;
const hasVerbLevelTts = this.synthesizer.vendor && this.synthesizer.vendor !== 'default'; const language = this.synthesizer.language || cs.speechSynthesisLanguage ;
const vendor = hasVerbLevelTts ? this.synthesizer.vendor : cs.speechSynthesisVendor ; const voice = this.synthesizer.voice || cs.speechSynthesisVoice ;
const language = hasVerbLevelTts ? this.synthesizer.language : cs.speechSynthesisLanguage ;
const voice = hasVerbLevelTts ? this.synthesizer.voice : cs.speechSynthesisVoice ;
const engine = this.synthesizer.engine || 'standard'; const engine = this.synthesizer.engine || 'standard';
const salt = cs.callSid; const salt = cs.callSid;
const credentials = cs.getSpeechCredentials(vendor, 'tts'); const credentials = cs.getSpeechCredentials(vendor, 'tts');