UNPKG

@onereach/step-voice

Version:
221 lines (220 loc) 9.55 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 getPhrasesForSpeechRec = (choices) => { const texts = lodash_1.default.flatMap(choices, (choice) => choice.options.map((opt) => opt.text)); return texts; }; const getAsrSettings = (choices, recognitionModel) => { // this step works only with the Google engine // hardcoded en-US lang will be changed to dynamic after step testing and design approval return { enabled: true, engine: 'google', language: 'en-US', config: { speechContexts: [ { phrases: getPhrasesForSpeechRec(choices) } ], recognitionModel } }; }; 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))); }; const alterRecognitionPhrases = (phrases, interpretation) => { if (interpretation.length !== 1) return; switch (interpretation[0]) { case '$POSTALCODE': phrases.forEach(p => { p.lexical = p.lexical.replace(/\s+/g, ''); }); break; case '$PERCENT': phrases.forEach(p => { p.lexical = p.lexical.replace(/\s+(?=%)/, ''); }); break; } }; class CustomVoiceInput extends voice_1.default { async runStep() { const call = await this.fetchData(); const { textType, tts, sensitiveData, noReplyDelay, usePromptsTriggers, recognitionModel, phraseList } = this.data; const exitExists = (exitId) => { return lodash_1.default.some(choices, (choice) => choice.exitId === exitId); }; const mainLeg = { exitId: '5886d1ee-db6d-45ca-a2ce-d586862c65ff', dtmf: null, grxml: '', text: '.+', grammar: {} }; const choices = this.buildChoices({ choices: [...this.data.choices, mainLeg] }); const ttsSettings = tts.getSettings(call.tts); const asrSettings = getAsrSettings(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: 'double-asr', params: { grammar, sections: [], resultInterpretation: this.data.resultInterpretation, tokenPhrases: phraseList || [] } }; // 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) { // digit recognition removed case 'recognition': { const params = event.params; const exitId = params.exitId; alterRecognitionPhrases(params.phrases, this.data.resultInterpretation); 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)); } else if (this.rptsHasMore({ repromptsList }) && (usePromptsTriggers ? isRepromptTrigger(phrases, this.data.promptsTriggers) : true)) { 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)); } 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', {}); } 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 = CustomVoiceInput; // --------------------------------------------------------------------