New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@voiceflow/natural-language-commander

Package Overview
Dependencies
Maintainers
13
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@voiceflow/natural-language-commander - npm Package Compare versions

Comparing version 0.3.2 to 0.4.2

3

dist/lib/constants.js

@@ -1,4 +0,1 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ANONYMOUS = "anonymous";
//# sourceMappingURL=constants.js.map

20

dist/lib/Matcher.d.ts

@@ -35,11 +35,11 @@ import { ISlotType, IIntent } from "./nlcInterfaces";

*/
private replaceCommonMispellings(utterance);
private replaceCommonMispellings;
/** Replace runs of spaces with the space character, for better matching. */
private replaceSpacesForRegexp(utterance);
private replaceSpacesForRegexp;
/** Escape braces that would cause a problem with regular expressions. */
private replaceBracesForRegexp(utterance);
private replaceBracesForRegexp;
/**
* Replace a solt with a regex capture group.
*/
private repaceSlotWithCaptureGroup(utterance, slotType, matchIndex, matchLength);
private repaceSlotWithCaptureGroup;
/**

@@ -51,19 +51,19 @@ * Check text for a slotType match.

*/
private checkSlotMatch(slotText, slotTypeName);
private checkSlotMatch;
/**
* Check the slot text against the slot regular expression, and return the text if it matches.
*/
private getRegexpSlot(slotText, slotType);
private getRegexpSlot;
/**
* Check if the string matches the slotType, and return the type's string if it does.
*/
private getStringSlot(slotText, slotType);
private getStringSlot;
/**
* Check if the string matches the slotType function, and return the function's return value if it does.
*/
private getFunctionSlotType(slotText, slotType);
private getFunctionSlotType;
/**
* Check if the string is contained in the string array, and return it if it does.
*/
private getListSlotType(slotText, slotType);
private getListSlotType;
/**

@@ -74,4 +74,4 @@ * Get the slot values in the order specified by an intent.

*/
private getOrderedSlots(slotMapping);
private getOrderedSlots;
}
export default Matcher;

@@ -91,3 +91,2 @@ "use strict";

const matchedSlots = {};
console.log(matches, this.slotMapping);
// Check each slot to see if it matches.

@@ -94,0 +93,0 @@ _.forEach(this.slotMapping, (slot, i) => {

@@ -20,2 +20,8 @@ export interface SlotTypeFunction {

type: string;
dialog?: {
prompt: string[];
confirm: string[];
utterances: string[];
};
required?: boolean;
}

@@ -35,49 +41,3 @@ /** An intent, to be passed to NLC. */

utterances: string[];
/** The callback to run when the intent matches. */
callback: ((...slotValues: any[]) => void) | ((data: any, ...slotValues: any[]) => void);
}
export interface IQuestion {
/** The question intent name. */
name: string;
/** The slot type the question is asking for. */
slotType: string;
/**
* Array of utterances to match. Slots must be named {Slot}.
* Defaults to checking for just the slot.
*/
utterances?: string[];
/**
* Asks the question. Called before setting up the answer listener.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
* @param slotValue - The transformed value of the answer's slot.
*/
questionCallback: (data: any) => void;
/**
* Called on sucessful match.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
* @param slotValue - The transformed value of the answer's slot.
*/
successCallback: (userId: any, slotValue: any) => void;
/**
* Called when the slot didn't match.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
*/
failCallback: (userId: any) => void;
/**
* If specified, NLC will listen for commands like "nevermind" or "cancel",
* and call this if the command matches them.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
*/
cancelCallback?: (userId: any) => void;
}
export interface IHandleCommandOptions {
command: string;
data?: any;
userId?: string;
}
export interface IAskOptions {
question: string;
data?: any;
userId?: string;
}
export interface ISlotFullfilment {

@@ -90,2 +50,3 @@ name: string;

slots: ISlotFullfilment[];
required?: IIntentSlot[];
}

@@ -0,4 +1,6 @@

/**
* Standard slot types for the NaturalLanguageCommander
* @module standardSlots
*/
import { ISlotType } from "./nlcInterfaces";
/** Commands to cancel a question. */
export declare const NEVERMIND: ISlotType;
/** A string of any length. */

@@ -5,0 +7,0 @@ export declare const STRING: ISlotType;

@@ -21,7 +21,2 @@ "use strict";

];
/** Commands to cancel a question. */
exports.NEVERMIND = {
type: "NEVERMIND",
matcher: ["nevermind", "never mind", "cancel", "exit", "back", "quit"]
};
/** A string of any length. */

@@ -28,0 +23,0 @@ exports.STRING = {

@@ -1,2 +0,2 @@

import { ISlotType, IHandleCommandOptions, IAskOptions, IIntent, IQuestion } from "./lib/nlcInterfaces";
import { ISlotType, IIntent, IIntentFullfilment } from "./lib/nlcInterfaces";
/** Holds registered natural language commands. */

@@ -8,4 +8,2 @@ declare class NaturalLanguageCommander {

private intents;
private questions;
private activeQuestions;
private matchers;

@@ -41,14 +39,2 @@ private notFoundCallback;

/**
* Register a question. Bound to this.
* @param intent
* @returns true if added, false if the intent name already exists.
*/
registerQuestion: (questionData: IQuestion) => boolean;
/**
* De-register a question. Bound to this.
* @param questionName
* @returns true if removed, false if the question doesn't exist.
*/
deregisterQuestion: (questionName: string) => boolean;
/**
* Register a callback to be called when a command doesn't match.

@@ -81,47 +67,12 @@ * Isn't called when an answer command doesn't match, since that is handled

removeUtterance(intentName: string, utterance: string): boolean;
handleDialog(match: IIntentFullfilment, command: string): IIntentFullfilment;
/**
* Handle a user's command by checking for a matching intent, and calling that intent's callback.
* @param data - Arbitrary data to pass to the callback.
* @param command - The command to match against.
* @param userId - any unqiue identifier for a user.
* @returns a promise resolved with the name of the matched intent, or rejected if no match.
* If the user had an active question, resolved or rejected with the name of the question intent.
*/
handleCommand(data: any, command: string): Promise<string>;
handleCommand(command: string): Promise<string>;
handleCommand(options: IHandleCommandOptions): Promise<string>;
/**
* Have NLC listen ask a question and listen for an answer for a given user.
* Calling this while a question is active for the user replace the old question.
* @param question - An intent name from a question intent.
* @param options.data - arbitrary data to pass along.
* @param options.userId - any unqiue identifier for a user.
* @param options.question - An intent name from a question intent.
* @returns false if questionName not found, true otherwise.
*/
ask(options: IAskOptions): Promise<boolean>;
ask(question: string): Promise<boolean>;
/**
* Cleans up a command for processing.
* @param command - the user's command.
*/
private cleanCommand(command);
private doesIntentExist(intentName);
/**
* Look up the active question for a user (if any). If the userId is undefined,
* check the anonymous user.
*/
private getActiveQuestion(userId);
/**
* Set the active question for a user.
*/
private setActiveQuestion(userId, question);
/**
* Deactive a question once the user has answered it.
*/
private finishQuestion(userId);
/** Handle a command for an active question. */
private handleQuestionAnswer(deferred, data, command, userId);
private cleanCommand;
private doesIntentExist;
/** Handle a command normally. */
private handleNormalCommand(data, command);
handleCommand(command: string): IIntentFullfilment | null;
getIntent(name: string): IIntent | null;
getIntents(): IIntent[];

@@ -132,2 +83,3 @@ getSlotTypes(): {

}
export = NaturalLanguageCommander;
export * from "./lib/nlcInterfaces";
export default NaturalLanguageCommander;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash");
const Deferred_1 = require("./lib/Deferred");
const immer_1 = require("immer");
const Matcher_1 = require("./lib/Matcher");
const Question_1 = require("./lib/Question");
const constants_1 = require("./lib/constants");
// Use setImmediate in node and FF, or the slower setTimeout otherwise,
// to delay a resolve so it is always async.
const delay = typeof setImmediate === "function" ? setImmediate : setTimeout;
/** Holds registered natural language commands. */

@@ -89,38 +85,5 @@ class NaturalLanguageCommander {

};
/**
* Register a question. Bound to this.
* @param intent
* @returns true if added, false if the intent name already exists.
*/
this.registerQuestion = (questionData) => {
// Don't allow duplicate intents.
if (this.doesIntentExist(questionData.name)) {
return false;
}
// Record the question name for checking for duplicates.
this.intentNames.push(questionData.name);
// Set up the question.
this.questions[questionData.name] = new Question_1.default(this, questionData);
return true;
};
/**
* De-register a question. Bound to this.
* @param questionName
* @returns true if removed, false if the question doesn't exist.
*/
this.deregisterQuestion = (questionName) => {
if (!this.doesIntentExist(questionName)) {
return false;
}
// Remove from the namelist.
this.intentNames = _.reject(this.intentNames, name => name === questionName);
// Remove from the questions dictionary.
delete this.questions[questionName];
return true;
};
this.slotTypes = {};
this.intentNames = [];
this.intents = [];
this.questions = {};
this.activeQuestions = {};
this.matchers = [];

@@ -196,85 +159,19 @@ // Noop the notFoundCallback.

}
handleCommand(dataOrCommandOrOptions, command) {
const deferred = new Deferred_1.default();
// Handle overload.
let data;
let userId;
if (_.isString(dataOrCommandOrOptions) && !command) {
// 2nd signature.
command = dataOrCommandOrOptions;
}
else if (command) {
// 1st signature.
data = dataOrCommandOrOptions;
}
else {
// 3rd signature.
command = dataOrCommandOrOptions.command;
data = dataOrCommandOrOptions.data;
userId = dataOrCommandOrOptions.userId;
}
if (!_.isString(command)) {
throw new Error(`NLC: ${command} must be a string!`);
}
// Clean up the input.
command = this.cleanCommand(command);
// Delay to ensure this is async.
delay(() => {
const commandResult = this.handleNormalCommand(data, command);
// If the command was successful:
if (commandResult) {
// Resolve with the intent name, for logging.
deferred.resolve(commandResult);
return;
}
// If not successful, check if there's an active question for the user.
if (this.getActiveQuestion(userId)) {
// If there is one, answer it and handle the deferred in there.
this.handleQuestionAnswer(deferred, data, command, userId);
}
else {
// Otherwise call the not found handler, since there was no match.
this.notFoundCallback(data);
// Also reject the promise for logging.
deferred.reject();
}
handleDialog(match, command) {
const intent = this.getIntent(match.intent);
if (!intent || !match.required.length)
throw new Error(`NLC: unable to handle dialog ${match.intent}`);
// If no dialog matched,
const otherIntent = this.handleCommand(command);
if (otherIntent)
return otherIntent;
// if nothing matches, take the raw string as the next required slot
return immer_1.default(match, (draft) => {
const required = draft.required.shift();
draft.slots.push({
name: required.name,
value: command
});
});
return deferred.promise;
}
ask(optionsOrQuestion) {
const deferred = new Deferred_1.default();
// Handle overload.
let data;
let userId;
let questionName;
if (_.isString(optionsOrQuestion)) {
questionName = optionsOrQuestion;
}
else {
userId = optionsOrQuestion.userId;
data = optionsOrQuestion.data;
questionName = optionsOrQuestion.question;
}
// Pull the question from the list of registered questions.
const question = this.questions[questionName];
// If the question exists, make it active.
if (question) {
// Make the question active.
this.setActiveQuestion(userId, question);
}
// Delay for async.
delay(() => {
if (question) {
// Ask the question after a delay.
question.ask(data || userId);
// Resolve.
deferred.resolve(true);
}
else {
// Reject the promise if the question isn't set up.
deferred.reject(false);
}
});
return deferred.promise;
}
/**

@@ -295,49 +192,9 @@ * Cleans up a command for processing.

}
/**
* Look up the active question for a user (if any). If the userId is undefined,
* check the anonymous user.
*/
getActiveQuestion(userId) {
return this.activeQuestions[userId || constants_1.ANONYMOUS];
}
/**
* Set the active question for a user.
*/
setActiveQuestion(userId, question) {
this.activeQuestions[userId || constants_1.ANONYMOUS] = question;
}
/**
* Deactive a question once the user has answered it.
*/
finishQuestion(userId) {
this.setActiveQuestion(userId, undefined);
}
/** Handle a command for an active question. */
handleQuestionAnswer(deferred, data, command, userId) {
// If this user has an active question, grab it.
const question = this.getActiveQuestion(userId);
const questionName = question.name;
// Finish the question before the answer is processed, in case the answer
// kicks off another question.
this.finishQuestion(userId);
// Try to answer the question with the command.
question
.answer(command, data || userId)
.then(() => {
// If the answer matched, resolve with the question name.
deferred.resolve(questionName);
})
.catch(() => {
this.finishQuestion(userId);
// If the answer failed, reject with the question name, so any
// logger knows what question failed.
deferred.reject(questionName);
});
}
/** Handle a command normally. */
handleNormalCommand(data, command) {
handleCommand(command) {
/** Flag if there was a match */
let foundMatch;
let foundMatch = null;
// Handle a normal command.
_.forEach(this.matchers, (matcher) => {
var _a;
/** The slots from the match or [], if the match was found. */

@@ -350,10 +207,5 @@ const orderedSlots = matcher.check(command);

slots: orderedSlots,
// slots in intent that are required and not part of orderedSlots
required: ((_a = matcher.intent.slots) === null || _a === void 0 ? void 0 : _a.filter(({ required, name }) => required && !orderedSlots.some((m) => m.name === name))) || []
};
const callSlots = orderedSlots.map(slot => slot.value);
if (data) {
// Add the data as the first arg, if specified.
callSlots.unshift(data);
}
// Call the callback with the slot values in order.
matcher.intent.callback.apply(null, callSlots);
// Flag that a match was found.

@@ -365,4 +217,21 @@ // Exit early.

});
// if the command is exactly the intent name
if (!foundMatch) {
_.forEach(this.intents, (intent) => {
var _a;
if (intent.intent === command) {
foundMatch = {
intent: intent.intent,
slots: [],
required: ((_a = intent.slots) === null || _a === void 0 ? void 0 : _a.filter(({ required }) => required)) || [],
};
return false;
}
});
}
return foundMatch;
}
getIntent(name) {
return this.intents.find((intent) => intent.intent === name);
}
getIntents() {

@@ -375,3 +244,3 @@ return this.intents;

}
module.exports = NaturalLanguageCommander;
exports.default = NaturalLanguageCommander;
//# sourceMappingURL=NaturalLanguageCommander.js.map
{
"name": "@voiceflow/natural-language-commander",
"version": "0.3.2",
"version": "0.4.2",
"description": "A tool for connecting natural language commands with callbacks.",

@@ -32,4 +32,4 @@ "homepage": "https://github.com/will-wow/natural-language-commander",

"devDependencies": {
"@types/chai": "^4.1.2",
"@types/chai-spies": "^0.0.0",
"@types/chai": "^4.1.2",
"@types/mocha": "^5.0.0",

@@ -40,4 +40,4 @@ "babel-plugin-transform-runtime": "^6.9.0",

"browserify": "^13.0.1",
"chai": "^4.1.2",
"chai-spies": "^1.0.0",
"chai": "^4.1.2",
"derequire": "^2.0.3",

@@ -50,6 +50,6 @@ "mkdirp": "^0.5.1",

"tsify": "^3.0.4",
"tslint": "^5.9.1",
"tslint-config-airbnb": "^5.8.0",
"tslint-config-prettier": "^1.10.0",
"tslint": "^5.9.1",
"typescript": "^2.8.1",
"typescript": "^3.8.3",
"uglify-js": "^2.6.2",

@@ -61,5 +61,6 @@ "uglifyify": "^3.0.1"

"babel-runtime": "^6.9.2",
"immer": "^6.0.2",
"lodash": "^4.17.5",
"moment-timezone": "^0.5.14",
"moment": "^2.22.0"
"moment": "^2.22.0",
"moment-timezone": "^0.5.14"
},

@@ -66,0 +67,0 @@ "typings": "./dist/NaturalLanguageCommander.d.ts",

@@ -70,10 +70,2 @@ # natural-language-commander

### Ask questions
Sometimes you may want to ask a user a specific question, and get back a
specific type of answer. Maybe the user didn't provide you enough information to work with,
or maybe you want them to go through a complex dialog. NLC helps you ask a
specific user a specific question, and decide if their answer is a valid answer,
is just a different command, is cancelling the question, or is invalid.
## Installation

@@ -121,7 +113,2 @@

### Questions
A _question_ is a named question you can register. When you later ask the question,
NLC will handle asking the user and validating their answer.
### Utterances

@@ -350,28 +337,2 @@

## Asking Questions
To register a question, call `nlc.registerQuestion` with a Question object with the attributes:
* `name` {string} - The name of the question. Used to ask the question later.
* `slotType` {string} - The name of a registered slot type. This is the type of answer that the question is looking for.
* `utterances?` {string[]} - An optional list of utterances to match the answer against.
These should include the slot as `{Slot}`. If not specified, the utterance is simply `'{Slot}'`.
* `questionCallback` {(data: any) => void} - Called when `nlc.ask()` is called. Used to ask the user the question.
* `successCallback` {(data: any, slot: any) => void} - Called when the answer matches.
* `cancelCallback?` {(data: any) => void} - Called when the answer was something like "cancel". If not specified,
handle "cancel" as a normal answer.
* `failCallback` {(data: any) => void} - Called when the answer does not match. Could be used to re-ask the question.
After registering a question, to actually ask that question, call `nlc.ask()` with the signatures:
* `nlc.ask(question: string)` - Just ask the question. The next `nlc.handleCommand()` that doesn't include
a userId will be treated as the answer. Used if the NLC instance only supports one user.
* `nlc.ask(data)` - Data should be an object with the attribtes:
* userId? {string} - A unique identifier for the user. The next `nlc.handleCommand()` that specifies this
userId will be treated as the answer.
* question {string} - The question name. If this is not found, the function will return false.
Note that if the user's next response is a valid command, the associated intent will handle
it, since the user is probably giving up on answering the question.
### Examples

@@ -378,0 +339,0 @@

@@ -138,4 +138,2 @@ import _ = require("lodash");

console.log(matches, this.slotMapping);
// Check each slot to see if it matches.

@@ -142,0 +140,0 @@ _.forEach(this.slotMapping, (slot: IIntentSlot, i: number) => {

@@ -23,2 +23,8 @@ export interface SlotTypeFunction {

type: string;
dialog?: {
prompt: string[];
confirm: string[];
utterances: string[];
};
required?: boolean;
}

@@ -39,55 +45,4 @@

utterances: string[];
/** The callback to run when the intent matches. */
callback:
| ((...slotValues: any[]) => void)
| ((data: any, ...slotValues: any[]) => void);
}
export interface IQuestion {
/** The question intent name. */
name: string;
/** The slot type the question is asking for. */
slotType: string;
/**
* Array of utterances to match. Slots must be named {Slot}.
* Defaults to checking for just the slot.
*/
utterances?: string[];
/**
* Asks the question. Called before setting up the answer listener.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
* @param slotValue - The transformed value of the answer's slot.
*/
questionCallback: (data: any) => void;
/**
* Called on sucessful match.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
* @param slotValue - The transformed value of the answer's slot.
*/
successCallback: (userId: any, slotValue: any) => void;
/**
* Called when the slot didn't match.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
*/
failCallback: (userId: any) => void;
/**
* If specified, NLC will listen for commands like "nevermind" or "cancel",
* and call this if the command matches them.
* @param data - Any arbitrary data passed to nlc.ask(). Defaults to the userId.
*/
cancelCallback?: (userId: any) => void;
}
export interface IHandleCommandOptions {
command: string;
data?: any;
userId?: string;
}
export interface IAskOptions {
question: string;
data?: any;
userId?: string;
}
export interface ISlotFullfilment {

@@ -101,2 +56,3 @@ name: string;

slots: ISlotFullfilment[];
required?: IIntentSlot[];
}

@@ -25,8 +25,2 @@ /**

/** Commands to cancel a question. */
export const NEVERMIND: ISlotType = {
type: "NEVERMIND",
matcher: ["nevermind", "never mind", "cancel", "exit", "back", "quit"]
};
/** A string of any length. */

@@ -33,0 +27,0 @@ export const STRING: ISlotType = {

import _ = require("lodash");
import Deferred from "./lib/Deferred";
import produce from 'immer';
import {
ISlotType,
IHandleCommandOptions,
IAskOptions,
IIntent,
IQuestion,
ISlotFullfilment,

@@ -14,14 +11,3 @@ IIntentFullfilment,

import Matcher from "./lib/Matcher";
import Question from "./lib/Question";
import { ANONYMOUS } from "./lib/constants";
interface IDelay {
(callback: () => void): number;
}
// Use setImmediate in node and FF, or the slower setTimeout otherwise,
// to delay a resolve so it is always async.
const delay: IDelay =
typeof setImmediate === "function" ? setImmediate : setTimeout;
/** Holds registered natural language commands. */

@@ -33,6 +19,4 @@ class NaturalLanguageCommander {

private intents: IIntent[];
private questions: { [name: string]: Question };
private activeQuestions: { [userId: string]: Question };
private matchers: Matcher[];
private notFoundCallback: (data: any) => void;
private notFoundCallback: () => void;

@@ -46,4 +30,2 @@ /**

this.intents = [];
this.questions = {};
this.activeQuestions = {};
this.matchers = [];

@@ -153,44 +135,2 @@ // Noop the notFoundCallback.

/**
* Register a question. Bound to this.
* @param intent
* @returns true if added, false if the intent name already exists.
*/
public registerQuestion = (questionData: IQuestion): boolean => {
// Don't allow duplicate intents.
if (this.doesIntentExist(questionData.name)) {
return false;
}
// Record the question name for checking for duplicates.
this.intentNames.push(questionData.name);
// Set up the question.
this.questions[questionData.name] = new Question(this, questionData);
return true;
};
/**
* De-register a question. Bound to this.
* @param questionName
* @returns true if removed, false if the question doesn't exist.
*/
public deregisterQuestion = (questionName: string): boolean => {
if (!this.doesIntentExist(questionName)) {
return false;
}
// Remove from the namelist.
this.intentNames = _.reject(
this.intentNames,
name => name === questionName
);
// Remove from the questions dictionary.
delete this.questions[questionName];
return true;
};
/**
* Register a callback to be called when a command doesn't match.

@@ -287,121 +227,21 @@ * Isn't called when an answer command doesn't match, since that is handled

/**
* Handle a user's command by checking for a matching intent, and calling that intent's callback.
* @param data - Arbitrary data to pass to the callback.
* @param command - The command to match against.
* @param userId - any unqiue identifier for a user.
* @returns a promise resolved with the name of the matched intent, or rejected if no match.
* If the user had an active question, resolved or rejected with the name of the question intent.
*/
public handleCommand(data: any, command: string): Promise<string>;
public handleCommand(command: string): Promise<string>;
public handleCommand(options: IHandleCommandOptions): Promise<string>;
public handleCommand(
dataOrCommandOrOptions: IHandleCommandOptions | string | any,
command?: string
): Promise<string> {
const deferred = new Deferred();
public handleDialog(match: IIntentFullfilment, command: string): IIntentFullfilment {
const intent = this.getIntent(match.intent);
if (!intent || !match.required.length) throw new Error(`NLC: unable to handle dialog ${match.intent}`);
// Handle overload.
let data: any;
let userId: string;
if (_.isString(dataOrCommandOrOptions) && !command) {
// 2nd signature.
command = dataOrCommandOrOptions;
} else if (command) {
// 1st signature.
data = dataOrCommandOrOptions;
} else {
// 3rd signature.
command = dataOrCommandOrOptions.command;
data = dataOrCommandOrOptions.data;
userId = dataOrCommandOrOptions.userId;
}
// If no dialog matched,
const otherIntent = this.handleCommand(command);
if (otherIntent) return otherIntent;
if (!_.isString(command)) {
throw new Error(`NLC: ${command} must be a string!`);
}
// Clean up the input.
command = this.cleanCommand(command);
// Delay to ensure this is async.
delay(() => {
const commandResult: IIntentFullfilment = this.handleNormalCommand(data, command);
// If the command was successful:
if (commandResult) {
// Resolve with the intent name, for logging.
deferred.resolve(commandResult);
return;
}
// If not successful, check if there's an active question for the user.
if (this.getActiveQuestion(userId)) {
// If there is one, answer it and handle the deferred in there.
this.handleQuestionAnswer(deferred, data, command, userId);
} else {
// Otherwise call the not found handler, since there was no match.
this.notFoundCallback(data);
// Also reject the promise for logging.
deferred.reject();
}
// if nothing matches, take the raw string as the next required slot
return produce(match, (draft) => {
const required = draft.required.shift();
draft.slots.push({
name: required.name,
value: command
});
});
return deferred.promise;
}
/**
* Have NLC listen ask a question and listen for an answer for a given user.
* Calling this while a question is active for the user replace the old question.
* @param question - An intent name from a question intent.
* @param options.data - arbitrary data to pass along.
* @param options.userId - any unqiue identifier for a user.
* @param options.question - An intent name from a question intent.
* @returns false if questionName not found, true otherwise.
*/
public ask(options: IAskOptions): Promise<boolean>;
public ask(question: string): Promise<boolean>;
public ask(optionsOrQuestion: IAskOptions | string): Promise<boolean> {
const deferred = new Deferred();
// Handle overload.
let data: any;
let userId: string;
let questionName: string;
if (_.isString(optionsOrQuestion)) {
questionName = optionsOrQuestion;
} else {
userId = optionsOrQuestion.userId;
data = optionsOrQuestion.data;
questionName = optionsOrQuestion.question;
}
// Pull the question from the list of registered questions.
const question: Question = this.questions[questionName];
// If the question exists, make it active.
if (question) {
// Make the question active.
this.setActiveQuestion(userId, question);
}
// Delay for async.
delay(() => {
if (question) {
// Ask the question after a delay.
question.ask(data || userId);
// Resolve.
deferred.resolve(true);
} else {
// Reject the promise if the question isn't set up.
deferred.reject(false);
}
});
return deferred.promise;
}
/**
* Cleans up a command for processing.

@@ -425,58 +265,6 @@ * @param command - the user's command.

/**
* Look up the active question for a user (if any). If the userId is undefined,
* check the anonymous user.
*/
private getActiveQuestion(userId: string): Question {
return this.activeQuestions[userId || ANONYMOUS];
}
/**
* Set the active question for a user.
*/
private setActiveQuestion(userId: string, question: Question): void {
this.activeQuestions[userId || ANONYMOUS] = question;
}
/**
* Deactive a question once the user has answered it.
*/
private finishQuestion(userId: string): void {
this.setActiveQuestion(userId, undefined);
}
/** Handle a command for an active question. */
private handleQuestionAnswer(
deferred: Deferred,
data: any,
command: string,
userId: string
): void {
// If this user has an active question, grab it.
const question: Question = this.getActiveQuestion(userId);
const questionName: string = question.name;
// Finish the question before the answer is processed, in case the answer
// kicks off another question.
this.finishQuestion(userId);
// Try to answer the question with the command.
question
.answer(command, data || userId)
.then(() => {
// If the answer matched, resolve with the question name.
deferred.resolve(questionName);
})
.catch(() => {
this.finishQuestion(userId);
// If the answer failed, reject with the question name, so any
// logger knows what question failed.
deferred.reject(questionName);
});
}
/** Handle a command normally. */
private handleNormalCommand(data: any, command: string): IIntentFullfilment {
public handleCommand(command: string): IIntentFullfilment | null {
/** Flag if there was a match */
let foundMatch: IIntentFullfilment;
let foundMatch: IIntentFullfilment | null = null;

@@ -493,15 +281,7 @@ // Handle a normal command.

slots: orderedSlots,
// slots in intent that are required and not part of orderedSlots
required: matcher.intent.slots?.filter(({ required, name }) => required && !orderedSlots.some((m) => m.name === name)) || []
};
const callSlots: any[] = orderedSlots.map(slot=>slot.value);
if (data) {
// Add the data as the first arg, if specified.
callSlots.unshift(data);
}
// Call the callback with the slot values in order.
matcher.intent.callback.apply(null, callSlots);
// Flag that a match was found.
// Exit early.

@@ -512,5 +292,23 @@ if (!(/^{(\w+?)}$/.test(matcher.originalUtterance))) return false;

// if the command is exactly the intent name
if (!foundMatch) {
_.forEach(this.intents, (intent) => {
if (intent.intent === command) {
foundMatch = {
intent: intent.intent,
slots: [],
required: intent.slots?.filter(({ required }) => required) || [],
}
return false;
}
})
}
return foundMatch;
}
public getIntent(name: string): IIntent | null {
return this.intents.find((intent) => intent.intent === name);
}
public getIntents() {

@@ -525,3 +323,3 @@ return this.intents;

// Use a standard npm export for post-transpile node compatibility.
export = NaturalLanguageCommander;
export * from "./lib/nlcInterfaces";
export default NaturalLanguageCommander;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc