UNPKG

7.06 kBJavaScriptView Raw
1'use strict';
2const _ = require('lodash');
3const nodeDebug = require('debug');
4const glob = require('glob');
5const path = require('path');
6
7const handleRequest = require('./handle-request');
8const createIntent = require('./create-intent');
9const createCustomSlot = require('./create-custom-slot');
10const generateSpeechAssets = require('./generate-speech-assets');
11const saveSpeechAssets = require('./save-speech-assets');
12const builtInIntentsMap = require('./built-in-intents-map');
13const createServer = require('./create-server');
14const parseError = require('./error-handler').parseError;
15
16const builtInIntentsList = _.keys(builtInIntentsMap).join(', ');
17const debug = nodeDebug('alexia:debug');
18
19/**
20 * Create new app
21 * @param {string} name - App name
22 * @param {Object} [options] - Additional app options
23 * @param {string} [options.version] - App version
24 * @param {string[]} [options.ids] - Array of app ids. Only requests with supported app ids will be handled
25 */
26module.exports = (name, options) => {
27 let app = {
28 name: name,
29 options: options,
30 intents: {},
31 customSlots: {},
32 actions: []
33 };
34
35 let handlers = {
36 onStart: () => 'Welcome',
37 onEnd: () => 'Bye',
38 defaultActionFail: () => 'Sorry, your command is invalid'
39 };
40
41 /**
42 * Sets handler to be called on application start
43 * @param {function} handler - Handler to be called when app is started without intent
44 */
45 app.onStart = (handler) => {
46 handlers.onStart = handler;
47 };
48
49 /**
50 * Sets handler to be called on application end
51 * @param {function} handler - Handler to be called when application is unexpectedly terminated
52 */
53 app.onEnd = (handler) => {
54 handlers.onEnd = handler;
55 };
56
57 /**
58 * Sets handler to be called on default action fail
59 * @param {function} handler - Default handler to be called when action can not be invoked
60 */
61 app.defaultActionFail = (handler) => {
62 handlers.defaultActionFail = handler;
63 };
64
65 /**
66 * Creates intent
67 * @param {string} name - Intent name. Should not be equal to built-in intent name. It is possible to use this function to create built-in intents but utterances are required argument and you need to specify full built-in intent name f.e. `AMAZON.StopIntent`. See `{@link app.builtInIntent}`. If not specified (null, undefined or empty string), automatically generated intent name is used but we recommend to name each intent
68 * @param {(string|string[])} richUtterances - one or more utterances. Utterances contain utterance description with slots types. Example: `My age is {age:Number}`
69 * @param {function} handler - Function to be called when intent is invoked
70 */
71 app.intent = (name, richUtterances, handler) => {
72 const intent = createIntent(app.intents, name, richUtterances, handler);
73 app.intents[intent.name] = intent;
74
75 return intent;
76 };
77
78 /**
79 * Creates built-int intent.
80 * Essentialy the same as `intent` but with optional `utterances` since we need to specify each built-in intent has its own set of default utterances you are not required to extend
81 * @param {string} name - Built-in Intent name. Must be one of: `cancel`, `help`, `next`, `no`, `pause`, `previous`, `repeat`, `resume`, `startOver`, `stop`, `yes`
82 * @param {(string|string[]|function)} [utterances] - one or more utterances without slots. Could be ommited and handler could be 2nd parameter instead
83 * @param {function} handler - Function to be called when intent is invoked
84 */
85 app.builtInIntent = (name, utterances, handler) => {
86 // Validate built-in intent name
87 if (!builtInIntentsMap[name]) {
88 const e = parseError(new Error(`Built-in Intent name ${name} is invalid. Please use one of: ${builtInIntentsList}`));
89 throw e;
90 }
91
92 // Shift ommited arguments (utternaces are optional)
93 if (!handler) {
94 handler = utterances;
95 utterances = undefined;
96 }
97
98 app.intent(name, utterances, handler);
99 };
100
101 /**
102 * Handles request and calls done when finished
103 * @param {Object} data - Request JSON to be handled.
104 * @param {Function} done - Callback to be called when request is handled. Callback is called with one argument - response JSON
105 */
106 app.handle = (data, done) => {
107 handleRequest(app, data, handlers, done);
108 };
109
110 /**
111 * Creates custom slot
112 * @param {string} name - Name of the custom slot
113 * @param {string[]} samples - Array of custom slot samples
114 */
115 app.customSlot = (name, samples) => {
116 const customSlot = createCustomSlot(app.customSlots, name, samples);
117 app.customSlots[name] = customSlot;
118 };
119
120 /**
121 * Creates action
122 * @param {string} action - Action object
123 * @param {string} action.from - Name of the intent to allow transition from
124 * @param {string} action.to - Name of th eintent to allow transition to
125 * @param {function} action.if - Function returning boolean whether this transition should be handled.
126 * @param {function} action.fail - Handler to be called if `action.if` returned `false`
127 */
128 app.action = (action) => {
129 app.actions.push({
130 from: typeof (action.from) === 'string' ? action.from : action.from.name,
131 to: typeof (action.to) === 'string' ? action.to : action.to.name,
132 if: action.if,
133 fail: action.fail
134 });
135 };
136
137 /**
138 * Generate speech assets object: {schema, utterances, customSlots}
139 */
140 app.speechAssets = () => {
141 return generateSpeechAssets(app);
142 };
143
144 /**
145 * Save speech assets to their respective files: intentSchema.json, utterances.txt, customSlots.txt
146 * @param {string} [directory] - directory folder name, defaults to '/speechAssets'
147 */
148 app.saveSpeechAssets = (directory) => {
149 const dir = directory || 'speechAssets';
150 const assets = generateSpeechAssets(app);
151 saveSpeechAssets(assets, dir);
152 };
153
154 /**
155 * Creates Hapi server with one route that handles all request for this app. Server must be started using `server.start()`
156 * @param {number} [options] - Server options
157 * @property {number} [options.path] - Path to run server route on. Defaults to `/`
158 * @property {number} [options.port] - Port to run server on. If not specified then `process.env.PORT` is used. Defaults to `8888`
159 * @returns {object} server
160 */
161 app.createServer = (options) => {
162 return createServer(app, options);
163 };
164
165 /**
166 * Registers all intents matching specified pattern
167 * @param {string} pattern - Pattern used for intent matching. Must be relative to project root. Example: 'src/intents/**.js'
168 */
169 app.registerIntents = (pattern) => {
170
171 const files = glob.sync(pattern);
172
173 if (files.length === 0) {
174 console.warn(`No intents found using pattern '${pattern}'`);
175 }
176
177 files.forEach(intentFile => {
178 debug(`Registering intent '${intentFile}'`);
179 require(path.relative(__dirname, intentFile))(app);
180 });
181
182 };
183
184 return app;
185};