UNPKG

@onereach/step-voice

Version:
276 lines (275 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const lodash_1 = tslib_1.__importDefault(require("lodash")); const voice_1 = tslib_1.__importDefault(require("./voice")); const digitWords = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' ]; const containsOnlyDigits = (stringsList) => { let isOnlyDigits = true; stringsList.forEach((str) => { if (digitWords.includes(String(str).toLowerCase())) { // Do nothing } else if (isNaN(str)) { isOnlyDigits = false; } }); return isOnlyDigits; }; const getPhrasesForSpeechRec = (choices) => { const texts = lodash_1.default.flatMap(choices, (choice) => choice.options.map((opt) => opt.text)); if (containsOnlyDigits(texts)) { return ['$OOV_CLASS_DIGIT_SEQUENCE']; } return texts; }; const getAsrSettings = (settings, choices, recognitionModel) => { if (settings.engine === 'google' || settings.engine === 'google_beta') { return { ...settings, config: { speechContexts: [ { phrases: getPhrasesForSpeechRec(choices) } ], recognitionModel } }; } return settings; }; const isRepromptTrigger = (recogResult, promptsTriggers) => { const phrases = recogResult.map((r) => { return r.lexical; }); const triggers = promptsTriggers.flatMap((trigger) => { if (lodash_1.default.isEmpty(trigger.grammar)) { return trigger.text.replace(/`/g, ''); } else { return trigger.grammar.value.map((v) => { return v.replace(/`/g, ''); }); } }); return !lodash_1.default.isEmpty(phrases.filter((e) => triggers.includes(e))); }; class Choice extends voice_1.default { async runStep() { const call = await this.fetchData(); const { textType, asr, tts, sensitiveData, noReplyDelay, usePromptsTriggers, recognitionModel, useInterspeechTimeout, interSpeechTimeout, longRecognition, usePromptsForUnrecognized } = this.data; const exitExists = (exitId) => { return lodash_1.default.some(choices, (choice) => choice.exitId === exitId); }; const choices = this.buildChoices({ choices: this.data.choices }); const ttsSettings = tts.getSettings(call.tts); const asrSettings = getAsrSettings(asr.getSettings(call.asr), choices, recognitionModel); const repromptsList = this.buildReprompts({ prompts: this.data.prompts }); const speechSections = this.buildSections({ sections: this.data.audio, textType, ttsSettings }); const grammar = { id: this.currentStepId, choices, asr: asrSettings }; const command = { name: 'speak', params: { grammar, dictation: longRecognition ? 'continuous' : useInterspeechTimeout, interSpeechTimeout: interSpeechTimeout * 1000, sections: [] } }; // There's a specific need to do so. There might be ${variable} section this.triggers.local(`in/voice/${call.id}`, async (event) => { const reportingSettingsKey = this.rptsStarted ? 'transcriptRepromptResponse' : 'transcriptResponse'; await this.handleInterruption({ call, event, speechSections, repromptsList, reportingSettingsKey: this.rptsStarted ? 'transcriptReprompt' : 'transcriptPrompt' }); switch (event.params.type) { case 'digit': case 'digits': { const params = event.params; const digit = params.digit ?? params.digits; const exitId = params.exitId; // On bargeIn, we should stop playback if (exitExists(exitId)) { const exitStepLabel = this.getExitStepLabel(exitId) ?? ''; const message = lodash_1.default.replace(exitStepLabel, /^(\d+\. )?(~ )?/, ''); await this.transcript(call, { keyPress: digit, message: message, reportingSettingsKey, action: 'Call DTMF', actionFromBot: false }); await this.resumeRecording(call, sensitiveData); return this.exitStep(exitId, this.exitChoiceData('dtmf', params), longRecognition); } else if ((lodash_1.default.isUndefined(usePromptsForUnrecognized) || usePromptsForUnrecognized) && this.rptsHasMore({ repromptsList })) { await this.transcript(call, { message: 'Unrecognized', keyPress: digit, reportingSettingsKey, action: 'Call DTMF', actionFromBot: false }); await this.rptsSend(call, { command, repromptsList, noReplyDelay, speechSections, textType, ttsSettings, sensitiveData }); return this.exitFlow(); } await this.transcript(call, { message: 'Not Recognized', keyPress: digit, reportingSettingsKey, action: 'Call DTMF', actionFromBot: false }); await this.resumeRecording(call, sensitiveData); return this.exitStep('unrecognized', this.exitChoiceData('dtmf', { digit }), longRecognition); } case 'recognition': { const params = event.params; const exitId = params.exitId; const phrases = params.phrases; if (lodash_1.default.isEmpty(phrases)) { await this.resumeRecording(call, sensitiveData); return this.exitStep('unrecognized', {}); } const voiceProcessResult = lodash_1.default.chain(phrases) .map((p) => p.lexical) .join(' | ') .value(); // On bargeIn, we should stop playback if (exitExists(exitId)) { await this.transcript(call, { message: lodash_1.default.replace(this.getExitStepLabel(exitId) ?? '', /^(\d+\. )?(~ )?/, ''), voiceProcessResult, reportingSettingsKey, action: 'Call Recognition', actionFromBot: false }); await this.resumeRecording(call, sensitiveData); // There might be hooks after this step which we will try to avoid return this.exitStep(exitId, this.exitChoiceData('voice', params), longRecognition); } else if (((lodash_1.default.isUndefined(usePromptsForUnrecognized) || usePromptsForUnrecognized) || (usePromptsTriggers ? isRepromptTrigger(phrases, this.data.promptsTriggers) : false)) && this.rptsHasMore({ repromptsList })) { await this.transcript(call, { message: 'Unrecognized', voiceProcessResult, reportingSettingsKey, action: 'Call Recognition', actionFromBot: false }); await this.rptsSend(call, { command, repromptsList, noReplyDelay, speechSections, textType, ttsSettings, sensitiveData }); return this.exitFlow(); } // There's no more reprompts, should move on to unrecognized direction await this.transcript(call, { message: 'Not Recognized', voiceProcessResult, reportingSettingsKey, action: 'Call Recognition', actionFromBot: false }); await this.resumeRecording(call, sensitiveData); // We might end up in same session return this.exitStep('unrecognized', this.exitChoiceData('voice', params), longRecognition); } case 'timeout': { if (this.rptsHasMore({ repromptsList })) { await this.transcript(call, { message: 'No Reply', reportingSettingsKey, action: 'Call Prompt', actionFromBot: false }); await this.rptsSend(call, { command, repromptsList, noReplyDelay, speechSections, textType, ttsSettings, sensitiveData }); return this.exitFlow(); } await this.transcript(call, { message: 'Not Replied', reportingSettingsKey, action: 'Call Prompt', actionFromBot: false }); await this.resumeRecording(call, sensitiveData); // We might end up in same session return this.exitStep('no reply', {}, longRecognition); } case 'hangup': { await this.handleHangup(call); return await this.waitConvEnd(); } case 'cancel': { return this.handleCancel(); } case 'error': return this.throwError(event.params.error); default: return this.exitFlow(); } }); this.triggers.otherwise(async () => { const eventId = await this.transcript(call, { sections: speechSections, reprompt: { maxAttempts: repromptsList.length, attempt: 0 }, reportingSettingsKey: 'transcriptPrompt', action: 'Call Prompt', actionFromBot: true }); command.params.reporterTranscriptEventId = eventId; command.params.sections = speechSections; command.params.timeout = this.rptsTimeout({ noReplyDelay, repromptsList, initial: true }); await this.pauseRecording(call, command, sensitiveData); return this.exitFlow(); }); } } exports.default = Choice;