node-nlp
Version:
Library for NLU (Natural Language Understanding) done in Node.js
771 lines (770 loc) • 29.3 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var Dialog_1 = require("./dialogs/Dialog");
var Message_1 = require("./Message");
var consts = require("./consts");
var sprintf = require("sprintf-js");
var events = require("events");
var async = require("async");
var Session = (function (_super) {
__extends(Session, _super);
function Session(options) {
var _this = _super.call(this) || this;
_this.options = options;
_this.msgSent = false;
_this._hasError = false;
_this._isReset = false;
_this.lastSendTime = new Date().getTime();
_this.batch = [];
_this.batchStarted = false;
_this.sendingBatch = false;
_this.inMiddleware = false;
_this._locale = null;
_this.localizer = null;
_this.logger = null;
_this.connector = options.connector;
_this.library = options.library;
_this.localizer = options.localizer;
_this.logger = options.logger;
if (typeof _this.options.autoBatchDelay !== 'number') {
_this.options.autoBatchDelay = 250;
}
return _this;
}
Session.prototype.toRecognizeContext = function () {
var _this = this;
return {
message: this.message,
userData: this.userData,
conversationData: this.conversationData,
privateConversationData: this.privateConversationData,
dialogData: this.dialogData,
localizer: this.localizer,
logger: this.logger,
dialogStack: function () { return _this.dialogStack(); },
preferredLocale: function () { return _this.preferredLocale(); },
gettext: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return Session.prototype.gettext.call(_this, args);
},
ngettext: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return Session.prototype.ngettext.call(_this, args);
},
locale: this.preferredLocale()
};
};
Session.prototype.dispatch = function (sessionState, message, done) {
var _this = this;
var index = 0;
var session = this;
var now = new Date().getTime();
var middleware = this.options.middleware || [];
var next = function () {
var handler = index < middleware.length ? middleware[index] : null;
if (handler) {
index++;
handler(session, next);
}
else {
_this.inMiddleware = false;
_this.sessionState.lastAccess = now;
done();
}
};
this.sessionState = sessionState || { callstack: [], lastAccess: now, version: 0.0 };
var cur = this.curDialog();
if (cur) {
this.dialogData = cur.state;
}
this.inMiddleware = true;
this.message = (message || { text: '' });
if (!this.message.type) {
this.message.type = consts.messageType;
}
var locale = this.preferredLocale();
this.localizer.load(locale, function (err) {
if (err) {
_this.error(err);
}
else {
next();
}
});
return this;
};
Session.prototype.error = function (err) {
var m = err.toString();
err = err instanceof Error ? err : new Error(m);
this.emit('error', err);
this.logger.error(this.dialogStack(), err);
this._hasError = true;
if (this.options.dialogErrorMessage) {
this.endConversation(this.options.dialogErrorMessage);
}
else {
var locale = this.preferredLocale();
this.endConversation(this.localizer.gettext(locale, 'default_error', consts.Library.system));
}
return this;
};
Session.prototype.preferredLocale = function (locale, callback) {
if (locale) {
this._locale = locale;
if (this.userData) {
this.userData[consts.Data.PreferredLocale] = locale;
}
if (this.localizer) {
this.localizer.load(locale, callback);
}
}
else if (!this._locale) {
if (this.userData && this.userData[consts.Data.PreferredLocale]) {
this._locale = this.userData[consts.Data.PreferredLocale];
}
else if (this.message && this.message.textLocale) {
this._locale = this.message.textLocale;
}
else if (this.localizer) {
this._locale = this.localizer.defaultLocale();
}
}
return this._locale;
};
Session.prototype.gettext = function (messageid) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return this.vgettext(this.curLibraryName(), messageid, args);
};
Session.prototype.ngettext = function (messageid, messageid_plural, count) {
var tmpl;
if (this.localizer && this.message) {
tmpl = this.localizer.ngettext(this.preferredLocale(), messageid, messageid_plural, count, this.curLibraryName());
}
else if (count == 1) {
tmpl = messageid;
}
else {
tmpl = messageid_plural;
}
return sprintf.sprintf(tmpl, count);
};
Session.prototype.save = function () {
this.logger.log(this.dialogStack(), 'Session.save()');
this.startBatch();
return this;
};
Session.prototype.send = function (message) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
args.unshift(this.curLibraryName(), message);
return Session.prototype.sendLocalized.apply(this, args);
};
Session.prototype.sendLocalized = function (libraryNamespace, message) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
this.msgSent = true;
if (message) {
var m;
if (typeof message == 'string' || Array.isArray(message)) {
m = this.createMessage(libraryNamespace, message, args);
}
else if (message.toMessage) {
m = message.toMessage();
}
else {
m = message;
}
this.prepareMessage(m);
this.batch.push(m);
this.logger.log(this.dialogStack(), 'Session.send()');
}
this.startBatch();
return this;
};
Session.prototype.say = function (text, speak, options) {
if (typeof speak === 'object') {
options = speak;
speak = null;
}
return this.sayLocalized(this.curLibraryName(), text, speak, options);
};
Session.prototype.sayLocalized = function (libraryNamespace, text, speak, options) {
this.msgSent = true;
var msg = new Message_1.Message(this).text(text).speak(speak).toMessage();
if (options) {
['attachments', 'attachmentLayout', 'entities', 'textFormat', 'inputHint'].forEach(function (field) {
if (options.hasOwnProperty(field)) {
msg[field] = options[field];
}
});
}
return this.sendLocalized(libraryNamespace, msg);
};
Session.prototype.sendTyping = function () {
this.msgSent = true;
var m = { type: 'typing' };
this.prepareMessage(m);
this.batch.push(m);
this.logger.log(this.dialogStack(), 'Session.sendTyping()');
return this;
};
Session.prototype.delay = function (delay) {
this.msgSent = true;
var m = { type: 'delay', value: delay };
this.prepareMessage(m);
this.batch.push(m);
this.logger.log(this.dialogStack(), 'Session.delay(%d)', delay);
return this;
};
Session.prototype.messageSent = function () {
return this.msgSent;
};
Session.prototype.beginDialog = function (id, args) {
this.logger.log(this.dialogStack(), 'Session.beginDialog(' + id + ')');
var id = this.resolveDialogId(id);
var dialog = this.findDialog(id);
if (!dialog) {
throw new Error('Dialog[' + id + '] not found.');
}
this.pushDialog({ id: id, state: {} });
this.startBatch();
dialog.begin(this, args);
return this;
};
Session.prototype.replaceDialog = function (id, args) {
this.logger.log(this.dialogStack(), 'Session.replaceDialog(' + id + ')');
var id = this.resolveDialogId(id);
var dialog = this.findDialog(id);
if (!dialog) {
throw new Error('Dialog[' + id + '] not found.');
}
this.popDialog();
this.pushDialog({ id: id, state: {} });
this.startBatch();
dialog.begin(this, args);
return this;
};
Session.prototype.endConversation = function (message) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
var m;
if (message) {
if (typeof message == 'string' || Array.isArray(message)) {
m = this.createMessage(this.curLibraryName(), message, args);
}
else if (message.toMessage) {
m = message.toMessage();
}
else {
m = message;
}
this.msgSent = true;
this.prepareMessage(m);
this.batch.push(m);
}
this.conversationData = {};
this.privateConversationData = {};
var code = this._hasError ? 'unknown' : 'completedSuccessfully';
var mec = { type: 'endOfConversation', code: code };
this.prepareMessage(mec);
this.batch.push(mec);
this.logger.log(this.dialogStack(), 'Session.endConversation()');
var ss = this.sessionState;
ss.callstack = [];
this.sendBatch();
return this;
};
Session.prototype.endDialog = function (message) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (typeof message === 'object' && (message.hasOwnProperty('response') || message.hasOwnProperty('resumed') || message.hasOwnProperty('error'))) {
console.warn('Returning results via Session.endDialog() is deprecated. Use Session.endDialogWithResult() instead.');
return this.endDialogWithResult(message);
}
var cur = this.curDialog();
if (cur) {
var m;
if (message) {
if (typeof message == 'string' || Array.isArray(message)) {
m = this.createMessage(this.curLibraryName(), message, args);
}
else if (message.toMessage) {
m = message.toMessage();
}
else {
m = message;
}
this.msgSent = true;
this.prepareMessage(m);
this.batch.push(m);
}
this.logger.log(this.dialogStack(), 'Session.endDialog()');
var childId = cur.id;
cur = this.popDialog();
this.startBatch();
if (cur) {
var dialog = this.findDialog(cur.id);
if (dialog) {
dialog.dialogResumed(this, { resumed: Dialog_1.ResumeReason.completed, response: true, childId: childId });
}
else {
this.error(new Error("Can't resume missing parent dialog '" + cur.id + "'."));
}
}
}
return this;
};
Session.prototype.endDialogWithResult = function (result) {
var cur = this.curDialog();
if (cur) {
result = result || {};
if (!result.hasOwnProperty('resumed')) {
result.resumed = Dialog_1.ResumeReason.completed;
}
result.childId = cur.id;
this.logger.log(this.dialogStack(), 'Session.endDialogWithResult()');
cur = this.popDialog();
this.startBatch();
if (cur) {
var dialog = this.findDialog(cur.id);
if (dialog) {
dialog.dialogResumed(this, result);
}
else {
this.error(new Error("Can't resume missing parent dialog '" + cur.id + "'."));
}
}
}
return this;
};
Session.prototype.cancelDialog = function (dialogId, replaceWithId, replaceWithArgs) {
var childId = typeof dialogId === 'number' ? this.sessionState.callstack[dialogId].id : dialogId;
var cur = this.deleteDialogs(dialogId);
if (replaceWithId) {
this.logger.log(this.dialogStack(), 'Session.cancelDialog(' + replaceWithId + ')');
var id = this.resolveDialogId(replaceWithId);
var dialog = this.findDialog(id);
this.pushDialog({ id: id, state: {} });
this.startBatch();
dialog.begin(this, replaceWithArgs);
}
else {
this.logger.log(this.dialogStack(), 'Session.cancelDialog()');
this.startBatch();
if (cur) {
var dialog = this.findDialog(cur.id);
if (dialog) {
dialog.dialogResumed(this, { resumed: Dialog_1.ResumeReason.canceled, response: null, childId: childId });
}
else {
this.error(new Error("Can't resume missing parent dialog '" + cur.id + "'."));
}
}
}
return this;
};
Session.prototype.reset = function (dialogId, dialogArgs) {
this.logger.log(this.dialogStack(), 'Session.reset()');
this._isReset = true;
this.sessionState.callstack = [];
if (!dialogId) {
dialogId = this.options.dialogId;
dialogArgs = this.options.dialogArgs;
}
this.beginDialog(dialogId, dialogArgs);
return this;
};
Session.prototype.isReset = function () {
return this._isReset;
};
Session.prototype.sendBatch = function (done) {
var _this = this;
this.logger.log(this.dialogStack(), 'Session.sendBatch() sending ' + this.batch.length + ' message(s)');
if (this.sendingBatch) {
this.batchStarted = true;
return;
}
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
this.batchTimer = null;
var batch = this.batch;
this.batch = [];
this.batchStarted = false;
this.sendingBatch = true;
var cur = this.curDialog();
if (cur) {
cur.state = this.dialogData;
}
this.onSave(function (err) {
if (!err) {
_this.onSend(batch, function (err, addresses) {
_this.onFinishBatch(function () {
if (_this.batchStarted) {
_this.startBatch();
}
if (done) {
done(err, addresses);
}
});
});
}
else {
_this.onFinishBatch(function () {
if (done) {
done(err, null);
}
});
}
});
};
Session.prototype.dialogStack = function (newStack) {
var stack;
if (newStack) {
stack = this.sessionState.callstack = newStack;
this.dialogData = stack.length > 0 ? stack[stack.length - 1].state : null;
}
else {
stack = this.sessionState.callstack || [];
if (stack.length > 0) {
stack[stack.length - 1].state = this.dialogData || {};
}
}
return stack.slice(0);
};
Session.prototype.clearDialogStack = function () {
this.sessionState.callstack = [];
this.dialogData = null;
return this;
};
Session.forEachDialogStackEntry = function (stack, reverse, fn) {
var step = reverse ? -1 : 1;
var l = stack ? stack.length : 0;
for (var i = step > 0 ? 0 : l - 1; i >= 0 && i < l; i += step) {
fn(stack[i], i);
}
};
Session.findDialogStackEntry = function (stack, dialogId, reverse) {
if (reverse === void 0) { reverse = false; }
var step = reverse ? -1 : 1;
var l = stack ? stack.length : 0;
for (var i = step > 0 ? 0 : l - 1; i >= 0 && i < l; i += step) {
if (stack[i].id === dialogId) {
return i;
}
}
return -1;
};
Session.activeDialogStackEntry = function (stack) {
return stack && stack.length > 0 ? stack[stack.length - 1] : null;
};
Session.pushDialogStackEntry = function (stack, entry) {
if (!entry.state) {
entry.state = {};
}
stack = stack || [];
stack.push(entry);
return entry;
};
Session.popDialogStackEntry = function (stack) {
if (stack && stack.length > 0) {
stack.pop();
}
return Session.activeDialogStackEntry(stack);
};
Session.pruneDialogStack = function (stack, start) {
if (stack && stack.length > 0) {
stack.splice(start);
}
return Session.activeDialogStackEntry(stack);
};
Session.validateDialogStack = function (stack, root) {
var valid = true;
Session.forEachDialogStackEntry(stack, false, function (entry) {
var pair = entry.id.split(':');
if (!root.findDialog(pair[0], pair[1])) {
valid = false;
}
});
return valid;
};
Session.prototype.routeToActiveDialog = function (recognizeResult) {
var dialogStack = this.dialogStack();
if (Session.validateDialogStack(dialogStack, this.library)) {
var active = Session.activeDialogStackEntry(dialogStack);
if (active) {
var dialog = this.findDialog(active.id);
dialog.replyReceived(this, recognizeResult);
}
else {
this.beginDialog(this.options.dialogId, this.options.dialogArgs);
}
}
else {
this.error(new Error('Invalid Dialog Stack.'));
}
};
Session.prototype.watch = function (variable, enable) {
if (enable === void 0) { enable = true; }
var name = variable.toLowerCase();
if (!this.userData.hasOwnProperty(consts.Data.DebugWatches)) {
this.userData[consts.Data.DebugWatches] = {};
}
if (watchableHandlers.hasOwnProperty(name)) {
var entry = watchableHandlers[name];
this.userData[consts.Data.DebugWatches][entry.name] = enable;
}
else {
throw new Error("Invalid watch statement. '" + variable + "' isn't watchable");
}
return this;
};
Session.prototype.watchList = function () {
var watches = [];
if (this.userData.hasOwnProperty(consts.Data.DebugWatches)) {
for (var name_1 in this.userData[consts.Data.DebugWatches]) {
if (this.userData[consts.Data.DebugWatches][name_1]) {
watches.push(name_1);
}
}
}
return watches;
};
Session.watchable = function (variable, handler) {
if (handler) {
watchableHandlers[variable.toLowerCase()] = { name: variable, handler: handler };
}
else {
var entry = watchableHandlers[variable.toLowerCase()];
if (entry) {
handler = entry.handler;
}
}
return handler;
};
Session.watchableList = function () {
var variables = [];
for (var name_2 in watchableHandlers) {
if (watchableHandlers.hasOwnProperty(name_2)) {
variables.push(watchableHandlers[name_2].name);
}
}
return variables;
};
Session.prototype.onSave = function (cb) {
var _this = this;
this.options.onSave(function (err) {
if (err) {
_this.logger.error(_this.dialogStack(), err);
switch (err.code || '') {
case consts.Errors.EBADMSG:
case consts.Errors.EMSGSIZE:
_this.userData = {};
_this.batch = [];
_this.endConversation(_this.options.dialogErrorMessage || 'Oops. Something went wrong and we need to start over.');
break;
}
}
cb(err);
});
};
Session.prototype.onSend = function (batch, cb) {
var _this = this;
if (batch && batch.length > 0) {
this.options.onSend(batch, function (err, responses) {
if (err) {
_this.logger.error(_this.dialogStack(), err);
}
cb(err, responses);
});
}
else {
cb(null, null);
}
};
Session.prototype.onFinishBatch = function (cb) {
var _this = this;
var ctx = this.toRecognizeContext();
async.each(this.watchList(), function (variable, cb) {
var entry = watchableHandlers[variable.toLowerCase()];
if (entry && entry.handler) {
try {
entry.handler(ctx, function (err, value) {
if (!err) {
_this.logger.dump(variable, value);
}
cb(err);
});
}
catch (e) {
cb(e);
}
}
else {
cb(new Error("'" + variable + "' isn't watchable."));
}
}, function (err) {
if (err) {
_this.logger.error(_this.dialogStack(), err);
}
_this.logger.flush(function (err) {
_this.sendingBatch = false;
if (err) {
console.error(err);
}
cb();
});
});
};
Session.prototype.startBatch = function () {
var _this = this;
this.batchStarted = true;
if (!this.sendingBatch) {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
}
this.batchTimer = setTimeout(function () {
_this.sendBatch();
}, this.options.autoBatchDelay);
}
};
Session.prototype.createMessage = function (localizationNamespace, text, args) {
var message = new Message_1.Message(this)
.text(this.vgettext(localizationNamespace, Message_1.Message.randomPrompt(text), args));
return message.toMessage();
};
Session.prototype.prepareMessage = function (msg) {
if (!msg.type) {
msg.type = 'message';
}
if (!msg.address) {
msg.address = this.message.address;
}
if (!msg.textLocale && this.message.textLocale) {
msg.textLocale = this.message.textLocale;
}
};
Session.prototype.vgettext = function (localizationNamespace, messageid, args) {
var tmpl;
if (this.localizer && this.message) {
tmpl = this.localizer.gettext(this.preferredLocale(), messageid, localizationNamespace);
}
else {
tmpl = messageid;
}
return args && args.length > 0 ? sprintf.vsprintf(tmpl, args) : tmpl;
};
Session.prototype.validateCallstack = function () {
var ss = this.sessionState;
for (var i = 0; i < ss.callstack.length; i++) {
var id = ss.callstack[i].id;
if (!this.findDialog(id)) {
return false;
}
}
return true;
};
Session.prototype.resolveDialogId = function (id) {
return id.indexOf(':') >= 0 ? id : this.curLibraryName() + ':' + id;
};
Session.prototype.curLibraryName = function () {
var cur = this.curDialog();
return cur && !this.inMiddleware ? cur.id.split(':')[0] : this.library.name;
};
Session.prototype.findDialog = function (id) {
var parts = id.split(':');
return this.library.findDialog(parts[0] || this.library.name, parts[1]);
};
Session.prototype.pushDialog = function (ds) {
var ss = this.sessionState;
var cur = this.curDialog();
if (cur) {
cur.state = this.dialogData || {};
}
ss.callstack.push(ds);
this.dialogData = ds.state || {};
return ds;
};
Session.prototype.popDialog = function () {
var ss = this.sessionState;
if (ss.callstack.length > 0) {
ss.callstack.pop();
}
var cur = this.curDialog();
this.dialogData = cur ? cur.state : null;
return cur;
};
Session.prototype.deleteDialogs = function (dialogId) {
var ss = this.sessionState;
var index = -1;
if (typeof dialogId === 'string') {
for (var i = ss.callstack.length - 1; i >= 0; i--) {
if (ss.callstack[i].id == dialogId) {
index = i;
break;
}
}
}
else {
index = dialogId;
}
if (index < 0 && index < ss.callstack.length) {
throw new Error('Unable to cancel dialog. Dialog[' + dialogId + '] not found.');
}
ss.callstack.splice(index);
var cur = this.curDialog();
this.dialogData = cur ? cur.state : null;
return cur;
};
Session.prototype.curDialog = function () {
var cur;
var ss = this.sessionState;
if (ss.callstack.length > 0) {
cur = ss.callstack[ss.callstack.length - 1];
}
return cur;
};
Session.prototype.getMessageReceived = function () {
console.warn("Session.getMessageReceived() is deprecated. Use Session.message.sourceEvent instead.");
return this.message.sourceEvent;
};
return Session;
}(events.EventEmitter));
exports.Session = Session;
var watchableHandlers = {
'userdata': { name: 'userData', handler: function (ctx, cb) { return cb(null, ctx.userData); } },
'conversationdata': { name: 'conversationData', handler: function (ctx, cb) { return cb(null, ctx.conversationData); } },
'privateconversationdata': { name: 'privateConversationData', handler: function (ctx, cb) { return cb(null, ctx.privateConversationData); } },
'dialogdata': { name: 'dialogData', handler: function (ctx, cb) { return cb(null, ctx.dialogData); } },
'dialogstack': { name: 'dialogStack', handler: function (ctx, cb) { return cb(null, ctx.dialogStack()); } },
'preferredlocale': { name: 'preferredLocale', handler: function (ctx, cb) { return cb(null, ctx.preferredLocale()); } },
'libraryname': { name: 'libraryName', handler: function (ctx, cb) { return cb(null, ctx.libraryName); } }
};