UNPKG

node-red-contrib-chatbot

Version:

REDBot a Chat bot for a full featured chat bot for Telegram, Facebook Messenger and Slack. Almost no coding skills required

199 lines (182 loc) 7.79 kB
const _ = require('underscore'); const { Language } = require('node-nlp'); const prettyjson = require('prettyjson'); const lcd = require('../lib/helpers/lcd'); const RegisterType = require('../lib/node-installer'); const MessageTemplate = require('../lib/message-template-async'); const { variable: isVariable } = require('../lib/helpers/validators'); const { isValidMessage, extractValue, isCommand } = require('../lib/helpers/utils'); const GlobalContextHelper = require('../lib/helpers/global-context-helper'); module.exports = function(RED) { const registerType = RegisterType(RED); const globalContextHelper = GlobalContextHelper(RED); function ChatBotNLPjs(config) { RED.nodes.createNode(this, config); const node = this; globalContextHelper.init(this.context().global); this.name = config.name; this.debug = config.debug; this.scoreThreshold = config.scoreThreshold; this.on('input', async function(msg, send, done) { // send/done compatibility for node-red < 1.0 send = send || function() { node.send.apply(node, arguments) }; done = done || function(error) { node.error.call(node, error, msg) }; // get the context to process: // if it's a valid message let content; if (isValidMessage(msg, node, { silent: true })) { // if it's a command, don't parse it, skip if (isCommand(msg) || msg.payload == null || !_.isString(msg.payload.content)) { send({ ...msg, previous: { ...msg.payload }, // store previous msg, use POP to retrieve }); done(); return; } content = msg.payload.content; } else if (_.isString(msg.payload)) { content = msg.payload; } else { // not a string, pass thru send({ ...msg, previous: { ...msg.payload }, // store previous msg, use POP to retrieve }); done('Incoming message is not a string'); return; } const template = MessageTemplate(msg, node); const name = extractValue('string', 'name', node, msg, false); const debug = extractValue('boolean', 'debug', node, msg, false); let language = extractValue('string', 'language', node, msg, false); let scoreThreshold = extractValue(['number', 'string', 'variable'], 'scoreThreshold', node, msg, false); // if not number, then evaluate it if (isVariable(scoreThreshold)) { scoreThreshold = await template.evaluate(scoreThreshold); } else if (_.isString(scoreThreshold)) { scoreThreshold = parseInt(scoreThreshold, 10); } if (isNaN(scoreThreshold) || scoreThreshold <= 0 || scoreThreshold > 100) { // eslint-disable-next-line no-console console.log(lcd.node({ scoreThreshold }, { title: 'Invalid scoreThreshold value, must be > 0 and <= 100', nodeId: node.id })); scoreThreshold = 50; } scoreThreshold = scoreThreshold / 100; // DOCS // entities // https://github.com/axa-group/nlp.js/blob/master/docs/v3/slot-filling.md#entities-with-the-same-name // get the right nlp model const manager = globalContextHelper.get('nlp_' + (!_.isEmpty(name) ? name : 'default')); if (manager == null) { done('NLP Model not found'); return; } // try to detect the language: // if not defined in the configuration then try to get from chat context (if exists) // or try to guess if (_.isEmpty(language)) { if (_.isFunction(msg.chat)) { language = await msg.chat().get('language'); if (debug) { // eslint-disable-next-line no-console console.log(lcd.white('[NLP] detecting with user language ') + lcd.green(language)); } } // if still empty if (_.isEmpty(language)) { const languageGuesser = new Language(); const guess = languageGuesser.guess(content); if (!_.isEmpty(guess)) { language = guess[0].alpha2; if (debug) { // eslint-disable-next-line no-console console.log(lcd.white('[NLP] gueesed language ') + lcd.green(language)); } } } } else { if (debug) { // eslint-disable-next-line no-console console.log(lcd.white('[NLP] detecting with language from configuration ') + lcd.green(language)); } } if (_.isEmpty(language)) { done('Unable to detect content language (from config, from chat context, from guess), skipping'); return; } // finally process const response = await manager.process(language, content); // extract vars const variables = {}; (response.entities || []).forEach(entity => { if (entity.type === 'regex') { variables[entity.entity] = entity.utteranceText; } else { const name = entity.alias != null ? entity.alias : entity.entity; const obj = entity.option ? entity.option : entity.resolution; obj.entity = entity.entity; // ensure all extracted entity has at least "value" and "entity" keeping all // other keys for retrocompatibility if (obj.value == null) { if (obj.values != null && Array.isArray(obj.values) && obj.values.length !== 0) { obj.value = obj.values[0].value; // handle the exception of duration as string if (obj.entity === 'duration') { obj.value = parseInt(obj.value, 10); } } } variables[name] = obj; } }); if (debug) { // eslint-disable-next-line no-console console.log(lcd.white('[NLP] ') + lcd.grey('Processing model ') + lcd.green(name)); // eslint-disable-next-line no-console console.log(' Input: ' + lcd.green(`"${content}"`) + lcd.white(` (${language})`)); // eslint-disable-next-line no-console console.log(lcd.white(' Score threshold: ') + lcd.green(scoreThreshold * 100) + lcd.grey(' %')); // eslint-disable-next-line no-console console.log(lcd.white(' Language guessed: ') + lcd.green(response.languageGuessed)); // eslint-disable-next-line no-console console.log(lcd.white(' Intent: ') + lcd.green(response.intent)); // eslint-disable-next-line no-console console.log(lcd.white(' Domain: ') + lcd.green(response.domain)); // eslint-disable-next-line no-console console.log(lcd.white(' Score: ') + lcd.green((response.score * 100).toFixed(1)) + lcd.grey(' %')); // eslint-disable-next-line no-console console.log( lcd.white(' Sentiment: ') + lcd.green(response.sentiment.vote) + ' score ' + lcd.green((response.sentiment.score * 100).toFixed(1)) + lcd.grey(' %') ); if (!_.isEmpty(variables)) { // eslint-disable-next-line no-console console.log(lcd.white(' Variables: ')); // eslint-disable-next-line no-console console.log(prettyjson.render(variables)) } } // if above the score let intent = 'None'; if (response.score > scoreThreshold) { intent = response.intent; } send({ ...msg, previous: msg.payload, // store previous msg, use POP to retrieve payload: { type: 'intent', score: response.score, isFallback: intent === 'None', language: response.localeIso2, intent, variables: !_.isEmpty(variables) ? variables : null } }); done(); }); } registerType('chatbot-nlpjs', ChatBotNLPjs); };