UNPKG

voxa-cli

Version:
295 lines (257 loc) 8.72 kB
/* * Copyright (c) 2018 Rain Agency <contact@rain.agency> * Author: Rain Agency <contact@rain.agency> * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* tslint:disable:no-empty no-submodule-imports */ import _ from "lodash"; import path from "path"; import uuid from "uuid/v5"; import { IDialog, IDialogIntent, IDialogSlot, IDialogSlotPrompt, IInteractionModel, IPrompt, IVariation } from "./AlexaInterfaces"; import { IIntent, ISlotDefinition, Schema } from "./Schema"; import { IVoxaSheet } from "./VoxaSheet"; const NAMESPACE = "alexa"; const AVAILABLE_LOCALES = [ "en-US", "en-GB", "en-CA", "en-AU", "en-IN", "de-DE", "ja-JP", "es-ES", "es-MX", "es-US", "fr-FR", "fr-CA", "it-IT", "pt-BR" ]; export class AlexaSchema extends Schema { public environment = "staging"; constructor(voxaSheets: IVoxaSheet[], interactionOptions: any) { super(NAMESPACE, AVAILABLE_LOCALES, voxaSheets, interactionOptions); } public validate() {} public build(locale: string, environment: string) { this.buildLanguageModel(locale, environment); this.buildPublishing(environment); } public buildPublishing(environment: string) { const manifest = this.mergeManifest(environment); _.set(manifest, "manifestVersion", "1.0"); this.fileContent.push({ path: path.join( this.interactionOptions.rootPath, this.interactionOptions.speechPath, this.NAMESPACE, `${_.kebabCase(environment)}-manifest.json` ), content: { manifest } }); } public contentLanguageModel(locale: string, environment: string): IInteractionModel { const invocation = _.find(this.invocations, { locale, environment }); const invocationName = _.get(invocation, "name", "Skill with no name"); const intents = this.getIntentsDefinition(locale, environment); const types = this.getSlotsByIntentsDefinition(locale, environment).map(rawSlot => ({ name: rawSlot.name, values: rawSlot.values.map(value => ({ name: value })) })); return { interactionModel: { languageModel: { invocationName, intents, types }, dialog: this.getDialogForLocaleAndEnvironment(locale, environment), prompts: this.getPromptsForLocaleAndEnvironment(locale, environment) } }; } public buildLanguageModel(locale: string, environment: string) { this.fileContent.push({ path: path.join( this.interactionOptions.rootPath, this.interactionOptions.speechPath, this.NAMESPACE, locale, `${_.kebabCase(environment)}-interaction.json` ), content: this.contentLanguageModel(locale, environment) }); const canFulfillIntents = _.chain(this.intentsByPlatformAndEnvironments(locale, environment)) .filter("canFulfillIntent") .map("name") .value(); this.fileContent.push({ path: path.join( this.interactionOptions.rootPath, this.interactionOptions.contentPath, `${_.kebabCase(environment)}-canfulfill-intents.json` ), content: canFulfillIntents }); } private getDialogForLocaleAndEnvironment( locale: string, environment: string ): IDialog | undefined { const dialog: IDialog | undefined = { intents: this.generateDialogModel(this.intentsByPlatformAndEnvironments(locale, environment)), delegationStrategy: "SKILL_RESPONSE" }; if (!dialog.intents.length) { return; } return dialog; } private getPromptsForLocaleAndEnvironment( locale: string, environment: string ): IPrompt[] | undefined { const prompts: IPrompt[] | undefined = this.generatePrompts( this.intentsByPlatformAndEnvironments(locale, environment) ); if (!prompts.length) { return; } return prompts; } private generateDialogModel(intents: IIntent[]): IDialogIntent[] { return _(intents) .filter(intentOrSlotRequireDialog) .map( (intent): IDialogIntent => { const prompts: IDialogSlotPrompt = {}; if (intent.confirmations.length > 0) { prompts.confirmation = getPromptId("Confirmation", "Intent", intent.confirmations); } return { name: intent.name, delegationStrategy: intent.delegationStrategy, confirmationRequired: intent.confirmationRequired, slots: this.generateDialogSlotModel(intent.slotsDefinition), prompts }; } ) .value(); } private generateDialogSlotModel(slots: ISlotDefinition[]): IDialogSlot[] { return _(slots) .map( (slot: ISlotDefinition): IDialogSlot => { const prompts: IDialogSlotPrompt = {}; if (slot.prompts.elicitation.length > 0) { prompts.elicitation = getPromptId("Elicitation", "Slot", slot.prompts.elicitation); } if (slot.prompts.confirmation.length > 0) { prompts.confirmation = getPromptId("Confirmation", "Slot", slot.prompts.confirmation); } return { name: slot.name.replace("{", "").replace("}", ""), type: slot.type, elicitationRequired: slot.requiresElicitation, confirmationRequired: slot.requiresConfirmation, prompts }; } ) .value(); } private generatePrompts(intents: IIntent[]): IPrompt[] { const intentPrompts: IPrompt[] = _(intents) .filter(intentHasPrompts) .map( (intent: IIntent): IPrompt => getPromptsObject("Confirmation", "Intent", intent.confirmations) ) .value(); const slotPrompts = this.generateSlotPrompts(intents); return _.concat(intentPrompts, slotPrompts); } private generateSlotPrompts(intents: IIntent[]): IPrompt[] { return _(intents) .map("slotsDefinition") .flatten() .filter(slotHasPrompts) .map((slot: ISlotDefinition): IPrompt[] => { const prompts: IPrompt[] = []; if (slot.prompts.confirmation.length > 0) { prompts.push(getPromptsObject("Confirmation", "Slot", slot.prompts.confirmation)); } if (slot.prompts.elicitation.length > 0) { prompts.push(getPromptsObject("Elicitation", "Slot", slot.prompts.elicitation)); } return prompts; }) .flatten() .value(); } } function hashObj(obj: any): string { return uuid(JSON.stringify(obj), uuid.DNS); } function intentOrSlotRequireDialog(intent: IIntent): boolean { if (intent.confirmationRequired || intent.delegationStrategy) { return true; } const slotRequiresDialog = _(intent.slotsDefinition) .map(slot => slot.requiresElicitation || slot.requiresConfirmation) .some(); return slotRequiresDialog; } function slotHasPrompts(slot: ISlotDefinition): boolean { return slot.prompts.confirmation.length > 0 || slot.prompts.elicitation.length > 0; } function intentHasPrompts(intent: IIntent): boolean { return intent.confirmations.length > 0; } function getPromptsObject( dialogType: "Elicitation" | "Confirmation", objectType: "Slot" | "Intent", data: string[] ): IPrompt { return { id: getPromptId(dialogType, objectType, data), variations: formatVariations(data) }; } function getPromptId( dialogType: "Elicitation" | "Confirmation", objectType: "Slot" | "Intent", data: string[] ): string { return `${dialogType}.${objectType}.${hashObj(data)}`; } function formatVariations(variations: string[]): IVariation[] { return _.map( variations, (variation): IVariation => ({ type: "PlainText", value: variation }) ); }