UNPKG

3.62 kBJavaScriptView Raw
1'use strict';
2const _ = require('lodash');
3const bases = require('bases');
4const builtInSlotsMap = require('./built-in-slots-map');
5const builtInIntentsMap = require('./built-in-intents-map');
6const validator = require('./validator');
7const parseError = require('./error-handler').parseError;
8
9/**
10 * Creates intent
11 * @param {Object[]} intents - Array of intents. Required for determining taken intent names
12 * @param {string} name - Name of the intent. If null or undefined, automatically generated intent name is used
13 * @param {(string|string[])} richUtterances - Utterance or array of rich utterances
14 * @param {function} handler - Function to be called when intent is invoked
15 */
16module.exports = (intents, name, richUtterances, handler) => {
17 // Convert utterances to array
18 richUtterances = _.isArray(richUtterances) ? richUtterances : [richUtterances];
19
20 // If intent name is not specified, try to generate unique one
21 if (!name) {
22 name = generateIntentName(intents);
23
24 } else if (!validator.isNameValid(name)) {
25 const e = parseError(new Error(`Intent name ${name} is invalid. Only lowercase and uppercase letters are allowed`));
26 throw e;
27 } else if (builtInIntentsMap[name]) {
28 // If built-in intent name was used map intent name to it
29 name = builtInIntentsMap[name];
30 }
31
32 // Transformed slots and utterances from richUtterances
33 let slots = [];
34 let utterances = [];
35
36 parseRichUtterances(richUtterances, slots, utterances);
37
38 return {
39 name: name,
40 slots: slots,
41 utterances: utterances,
42 handler: handler
43 };
44};
45
46const generateIntentName = (intents) => {
47 let position = 0;
48 let generatedName;
49
50 // While generated name is not already used and generatedName is not built-in intent (just in case)
51 while (intents[(generatedName = bases.toBase52(position++))] && !builtInIntentsMap[generatedName]);
52
53 return generatedName;
54};
55
56const parseRichUtterances = (richUtterances, slots, utterances) => {
57 // Iterate over each rich utterance and transform it by removing slots description
58 _.each(richUtterances, function (utterance) {
59 var matches = findUtteranceMatches(utterance);
60
61 _.each(matches, function (match) {
62 const slotName = match[1];
63 const slotType = match[2];
64
65 // Prevent duplicate slot definition
66 if (!_.find(slots, {name: slotName})) {
67
68 // Remember slot type
69 slots.push({
70 name: slotName,
71 type: transformSlotType(slotType)
72 });
73 }
74
75 // Replace utterance slot type (there could be multiple slots in utterance)
76 utterance = utterance.replace(match[0], '{' + slotName + '}');
77 });
78
79 if (validator.isUtteranceValid(utterance)) {
80 // Remember utterance
81 utterances.push(utterance);
82 } else {
83 const e = parseError(new Error(`Sample utterance: '${utterance}' is not valid. Each sample utterance must consist only of alphabet characters, spaces, dots, hyphens, brackets and single quotes`));
84 throw e;
85 }
86
87 });
88};
89
90const transformSlotType = (type) => {
91 const transformedType = builtInSlotsMap[type];
92 return transformedType || type;
93};
94
95const findUtteranceMatches = (utterance) => {
96 // Example: for 'move forward by {value:Number}' we get:
97 // [[ '{value:Number}', 'value', 'Number', index: 16, input: 'move forward by {value:Number}' ]]
98 const myregex = /{(.*?):(.*?)\}/gmi;
99 let result;
100 let allMatches = [];
101
102 while ((result = myregex.exec(utterance)) != null) {
103 allMatches.push(result);
104 }
105
106 return allMatches;
107};