@broadly/botpress-hitl
Version:
Official HITL (Human In The Loop) module for Botpress
626 lines (509 loc) • 19.7 kB
JavaScript
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/Users/jano/code/broadly/botpress-private/packages/functionals/botpress-hitl";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("lodash");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(2);
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
;
var _botpressVersionManager = __webpack_require__(3);
var _botpressVersionManager2 = _interopRequireDefault(_botpressVersionManager);
var _db = __webpack_require__(4);
var _db2 = _interopRequireDefault(_db);
var _lodash = __webpack_require__(0);
var _lodash2 = _interopRequireDefault(_lodash);
var _path = __webpack_require__(8);
var _path2 = _interopRequireDefault(_path);
var _fs = __webpack_require__(9);
var _fs2 = _interopRequireDefault(_fs);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
// TODO: Cleanup old sessions
// TODO: If messages count > X, delete some
var db = null;
var config = null;
var incomingMiddleware = function incomingMiddleware(event, next) {
if (!db) {
return next();
}
if (_lodash2.default.includes(['delivery', 'read'], event.type)) {
return next();
}
return db.getUserSession(event).then(function (session) {
if (!session) {
return next();
}
if (session.is_new_session) {
event.bp.events.emit('hitl.session', session);
}
return db.appendMessageToSession(event, session.id, 'in').then(function (message) {
event.bp.events.emit('hitl.message', message);
if ((!!session.paused || config.paused) && _lodash2.default.includes(['text', 'message'], event.type)) {
event.bp.logger.debug('[hitl] Session paused, message swallowed:', event.text);
// the session or bot is paused, swallow the message
return;
} else {
next();
}
});
});
};
var outgoingMiddleware = function outgoingMiddleware(event, next) {
if (!db) {
return next();
}
return db.getUserSession(event).then(function (session) {
if (!session) {
return next();
}
if (session.is_new_session) {
event.bp.events.emit('hitl.session', session);
}
return db.appendMessageToSession(event, session.id, 'out').then(function (message) {
event.bp.events.emit('hitl.message', message);
next();
});
});
};
module.exports = {
config: {
sessionExpiry: { type: 'string', default: '3 days' },
paused: { type: 'bool', default: false, env: 'BOTPRESS_HITL_PAUSED' }
},
init: function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(bp, configurator) {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
(0, _botpressVersionManager2.default)(bp, __dirname);
bp.middlewares.register({
name: 'hitl.captureInMessages',
type: 'incoming',
order: 2,
handler: incomingMiddleware,
module: 'botpress-hitl',
description: 'Captures incoming messages and if the session if paused, swallow the event.'
});
bp.middlewares.register({
name: 'hitl.captureOutMessages',
type: 'outgoing',
order: 50,
handler: outgoingMiddleware,
module: 'botpress-hitl',
description: 'Captures outgoing messages to show inside HITL.'
});
_context.next = 5;
return configurator.loadAll();
case 5:
config = _context.sent;
bp.db.get().then(function (knex) {
return db = (0, _db2.default)(knex);
}).then(function () {
return db.initialize();
});
case 7:
case 'end':
return _context.stop();
}
}
}, _callee, undefined);
}));
return function init(_x, _x2) {
return _ref.apply(this, arguments);
};
}(),
ready: function ready(bp) {
var _this = this;
bp.hitl = {
pause: function pause(platform, userId) {
return db.setSessionPaused(true, platform, userId, 'code').then(function (sessionId) {
bp.events.emit('hitl.session', { id: sessionId });
bp.events.emit('hitl.session.changed', { id: sessionId, paused: 1 });
});
},
unpause: function unpause(platform, userId) {
return db.setSessionPaused(false, platform, userId, 'code').then(function (sessionId) {
bp.events.emit('hitl.session', { id: sessionId });
bp.events.emit('hitl.session.changed', { id: sessionId, paused: 0 });
});
},
isPaused: function isPaused(platform, userId) {
return db.isSessionPaused(platform, userId);
}
};
var router = bp.getRouter('botpress-hitl');
router.get('/sessions', function (req, res) {
db.getAllSessions(req.query.onlyPaused === 'true').then(function (sessions) {
return res.send(sessions);
});
});
router.get('/sessions/:sessionId', function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(req, res) {
var messages, _ref3, userId, tags;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return db.getSessionData(req.params.sessionId);
case 2:
messages = _context2.sent;
_context2.next = 5;
return db.getSession(req.params.sessionId);
case 5:
_ref3 = _context2.sent;
userId = _ref3.userId;
_context2.next = 9;
return bp.users.getTags(userId);
case 9:
tags = _context2.sent;
res.send({ messages: messages, tags: tags });
case 11:
case 'end':
return _context2.stop();
}
}
}, _callee2, _this);
}));
return function (_x3, _x4) {
return _ref2.apply(this, arguments);
};
}());
router.post('/sessions/:sessionId/message', function (req, res) {
var message = req.body.message;
db.getSession(req.params.sessionId).then(function () {
var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(session) {
var event;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
event = {
type: 'text',
platform: session.platform,
raw: { to: session.userId, message: message },
user: { id: session.userId },
text: message
};
_context3.next = 3;
return bp.middlewares.sendOutgoing(event);
case 3:
res.sendStatus(200);
case 4:
case 'end':
return _context3.stop();
}
}
}, _callee3, _this);
}));
return function (_x5) {
return _ref4.apply(this, arguments);
};
}());
});
// TODO post /sessions/:id/typing
router.post('/sessions/:sessionId/pause', function (req, res) {
db.setSessionPaused(true, null, null, 'operator', req.params.sessionId).then(function (sessionId) {
bp.events.emit('hitl.session', { id: sessionId });
bp.events.emit('hitl.session.changed', { id: sessionId, paused: 1 });
}).then(res.sendStatus(200));
});
router.post('/sessions/:sessionId/unpause', function (req, res) {
db.setSessionPaused(false, null, null, 'operator', req.params.sessionId).then(function (sessionId) {
bp.events.emit('hitl.session', { id: sessionId });
bp.events.emit('hitl.session.changed', { id: sessionId, paused: 0 });
}).then(res.sendStatus(200));
});
}
};
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = require("botpress-version-manager");
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
;
var getUserSession = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(event) {
var userId;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
userId = event.user && event.user.id || event.raw.to;
if (userId) {
_context.next = 3;
break;
}
return _context.abrupt('return', null);
case 3:
return _context.abrupt('return', knex('hitl_sessions').where({ platform: event.platform, userId: userId }).select('*').limit(1).then(function (users) {
if (!users || users.length === 0) {
return createUserSession(event);
} else {
return users[0];
}
}));
case 4:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function getUserSession(_x) {
return _ref.apply(this, arguments);
};
}();
var _bluebird = __webpack_require__(5);
var _bluebird2 = _interopRequireDefault(_bluebird);
var _moment = __webpack_require__(6);
var _moment2 = _interopRequireDefault(_moment);
var _lodash = __webpack_require__(0);
var _lodash2 = _interopRequireDefault(_lodash);
var _botpress = __webpack_require__(7);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new _bluebird2.default(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return _bluebird2.default.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
var knex = null;
function initialize() {
if (!knex) {
throw new Error('you must initialize the database before');
}
return (0, _botpress.DatabaseHelpers)(knex).createTableIfNotExists('hitl_sessions', function (table) {
table.increments('id').primary();
table.string('platform');
table.string('userId');
table.string('full_name');
table.string('user_image_url');
table.timestamp('last_event_on');
table.timestamp('last_heard_on');
table.boolean('paused');
table.string('paused_trigger');
}).then(function () {
return (0, _botpress.DatabaseHelpers)(knex).createTableIfNotExists('hitl_messages', function (table) {
table.increments('id').primary();
table.integer('session_id').references('hitl_sessions.id').onDelete('CASCADE');
table.string('type');
table.string('text');
table.jsonb('raw_message');
table.enu('direction', ['in', 'out']);
table.timestamp('ts');
});
});
}
function createUserSession(event) {
var profileUrl = null;
var full_name = '#' + Math.random().toString().substr(2);
if (event.user && event.user.first_name && event.user.last_name) {
profileUrl = event.user.profile_pic || event.user.picture_url;
full_name = event.user.first_name + ' ' + event.user.last_name;
}
var session = {
platform: event.platform,
userId: event.user.id,
user_image_url: profileUrl,
last_event_on: (0, _botpress.DatabaseHelpers)(knex).date.now(),
last_heard_on: (0, _botpress.DatabaseHelpers)(knex).date.now(),
paused: 0,
full_name: full_name,
paused_trigger: null
};
return knex('hitl_sessions').insert(session).returning('id').then(function (results) {
session.id = results[0];
session.is_new_session = true;
}).then(function () {
return knex('hitl_sessions').where({ id: session.id }).then().get(0);
}).then(function (db_session) {
return Object.assign({}, session, db_session);
});
}
function getSession(sessionId) {
return knex('hitl_sessions').where({ id: sessionId }).select('*').limit(1).then(function (users) {
if (!users || users.length === 0) {
return null;
} else {
return users[0];
}
});
}
function toPlainObject(object) {
// trims SQL queries from objects
return _lodash2.default.mapValues(object, function (v) {
return v.sql ? v.sql : v;
});
}
function appendMessageToSession(event, sessionId, direction) {
var message = {
session_id: sessionId,
type: event.type,
text: event.text,
raw_message: event.raw,
direction: direction,
ts: (0, _botpress.DatabaseHelpers)(knex).date.now()
};
var update = { last_event_on: (0, _botpress.DatabaseHelpers)(knex).date.now() };
if (direction === 'in') {
update.last_heard_on = (0, _botpress.DatabaseHelpers)(knex).date.now();
}
return knex('hitl_messages').insert(message).then(function () {
return knex('hitl_sessions').where({ id: sessionId }).update(update).then(function () {
return toPlainObject(message);
});
});
}
function setSessionPaused(paused, platform, userId, trigger) {
var sessionId = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
if (sessionId) {
return knex('hitl_sessions').where({ id: sessionId }).update({ paused: paused ? 1 : 0, paused_trigger: trigger }).then(function () {
return parseInt(sessionId);
});
} else {
return knex('hitl_sessions').where({ userId: userId, platform: platform }).update({ paused: paused ? 1 : 0, paused_trigger: trigger }).then(function () {
return knex('hitl_sessions').where({ userId: userId, platform: platform }).select('id');
}).then(function (sessions) {
return parseInt(sessions[0].id);
});
}
}
function isSessionPaused(platform, userId) {
var sessionId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var toBool = function toBool(s) {
return (0, _botpress.DatabaseHelpers)(knex).bool.parse(s);
};
if (sessionId) {
return knex('hitl_sessions').where({ id: sessionId }).select('paused').then().get(0).then(function (s) {
return s && toBool(s.paused);
});
} else {
return knex('hitl_sessions').where({ userId: userId, platform: platform }).select('paused').then().get(0).then(function (s) {
return s && toBool(s.paused);
});
}
}
function getAllSessions(onlyPaused) {
var condition = '';
if (onlyPaused === true) {
condition = 'hitl_sessions.paused = ' + (0, _botpress.DatabaseHelpers)(knex).bool.true();
}
return knex.select('*').from(function () {
this.select([knex.raw('max(id) as mId'), 'session_id', knex.raw('count(*) as count')]).from('hitl_messages').where({ type: 'text' }).groupBy('session_id').as('q1');
}).join('hitl_messages', knex.raw('q1.mId'), 'hitl_messages.id').join('hitl_sessions', knex.raw('q1.session_id'), 'hitl_sessions.id').whereRaw(condition).orderBy('hitl_sessions.last_event_on', 'desc').limit(400).then(function (results) {
return {
total: 0,
sessions: results
};
});
}
function getSessionData(sessionId) {
return knex('hitl_sessions').where({ session_id: sessionId }).join('hitl_messages', 'hitl_messages.session_id', 'hitl_sessions.id').orderBy('hitl_messages.id', 'desc').limit(100).select('*').then(function (messages) {
return _lodash2.default.orderBy(messages, ['id'], ['asc']);
});
}
module.exports = function (k) {
knex = k;
return {
initialize: initialize,
getUserSession: getUserSession,
setSessionPaused: setSessionPaused,
appendMessageToSession: appendMessageToSession,
getAllSessions: getAllSessions,
getSessionData: getSessionData,
getSession: getSession,
isSessionPaused: isSessionPaused
};
};
/***/ }),
/* 5 */
/***/ (function(module, exports) {
module.exports = require("bluebird");
/***/ }),
/* 6 */
/***/ (function(module, exports) {
module.exports = require("moment");
/***/ }),
/* 7 */
/***/ (function(module, exports) {
module.exports = require("botpress");
/***/ }),
/* 8 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 9 */
/***/ (function(module, exports) {
module.exports = require("fs");
/***/ })
/******/ ]);
//# sourceMappingURL=node.bundle.js.map