diff --git a/package-lock.json b/package-lock.json index 78011f74..8f997469 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "botserver", - "version": "2.0.12", + "version": "2.0.42", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -57,9 +57,9 @@ } }, "@azure/cosmos": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.2.tgz", - "integrity": "sha512-nS02/LyjE22osouSJGWw6DqJ34tbkEgAcysrbKyDzyiqdDKNprP8sAU43WGVr6Jxjw8jGBzg6rkHz9A+IvPcdg==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.9.3.tgz", + "integrity": "sha512-1mh8a6LAIykz24tJvQpafXiABUfq+HSAZBFJVZXea0Rd0qG8Ia9z8AK9FtPbC1nPvDC2RID2mRIjJvYbxRM/BA==", "requires": { "@types/debug": "^4.1.4", "debug": "^4.1.1", @@ -71,7 +71,7 @@ "semaphore": "^1.0.5", "tslib": "^2.0.0", "universal-user-agent": "^6.0.0", - "uuid": "^8.1.0" + "uuid": "^8.3.0" }, "dependencies": { "debug": { @@ -2526,9 +2526,9 @@ "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" }, "@types/documentdb": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/@types/documentdb/-/documentdb-1.10.7.tgz", - "integrity": "sha512-IOob9vJEzPqKzSQdgcUrmKBZNx9ngwAB8fldI4B92/mPh3wHgTjRDW7CJDF9yDMOwvr9fM3+hq/d8I4RmbWCYQ==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/@types/documentdb/-/documentdb-1.10.8.tgz", + "integrity": "sha512-GkOXovVMlMVTYkPomq9rOI79DmVOMZ0TDziL3H3TSlhUSm1/txi5qA49H/qZRDFsExagjnf5Cd/4xF8mXVxubw==", "requires": { "@types/node": "*" } @@ -4484,9 +4484,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.40", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.40.tgz", - "integrity": "sha512-3hZT2z2/531A5pc8hYhn1gU5Qb1SIRSgMLQ6zuHA5xtt16lWAxUGprtr8lJuc9zNJMXEIIBWfSnzqBP/4mglpA==" + "version": "10.17.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.44.tgz", + "integrity": "sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw==" } } }, @@ -4608,9 +4608,9 @@ } }, "botlib": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/botlib/-/botlib-1.6.5.tgz", - "integrity": "sha512-0K+BNnHGwryID0MCG8s3s9CVGzjyBLph7ngtZp1oBNwvfcMinGGiOtbcV3CRHdiOypPr/FFuKyqe+kBVTbOtlQ==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/botlib/-/botlib-1.6.7.tgz", + "integrity": "sha512-KE2WnfqMciWGGZpYcsIrjgwqnZriNhbj5kxwtsrQJbsfmSr4SToPWcP0wPlSuaa2DubtnpYcEjSzmydHH7Ag0A==", "requires": { "async": "3.1.0", "botbuilder": "4.7.0", @@ -8562,9 +8562,9 @@ }, "dependencies": { "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" } } }, @@ -12527,6 +12527,15 @@ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-1.1.0.tgz", "integrity": "sha512-dEYmUqjtbivotqjraOe8UvhT/poFfog1BQRNsZm/MSEDDESk2cQ1tvD8kGyuN07TM/zoW+n42odL8zTeJupYdQ==" }, + "node-cron": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-2.0.3.tgz", + "integrity": "sha512-eJI+QitXlwcgiZwNNSRbqsjeZMp5shyajMR81RZCqeW0ZDEj4zU9tpd4nTh/1JsBiKbF8d08FCewiipDmVIYjg==", + "requires": { + "opencollective-postinstall": "^2.0.0", + "tz-offset": "0.0.1" + } + }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -16242,6 +16251,11 @@ "mimic-fn": "^2.1.0" } }, + "opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==" + }, "opn": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", @@ -21082,6 +21096,11 @@ "function.name": "^1.0.3" } }, + "tz-offset": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tz-offset/-/tz-offset-0.0.1.tgz", + "integrity": "sha512-kMBmblijHJXyOpKzgDhKx9INYU4u4E1RPMB0HqmKSgWG8vEcf3exEfLh4FFfzd3xdQOw9EuIy/cP0akY6rHopQ==" + }, "uglify-js": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", diff --git a/package.json b/package.json index 22f15312..db650e81 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "botbuilder-ai": "4.7.0", "botbuilder-dialogs": "4.7.0", "botframework-connector": "4.7.0", - "botlib": "1.6.5", + "botlib": "1.6.7", "cli-spinner": "0.2.10", "core-js": "^3.6.5", "dotenv-extended": "2.8.0", diff --git a/packages/core.gbapp/dialogs/LanguageDialog.ts b/packages/core.gbapp/dialogs/LanguageDialog.ts new file mode 100644 index 00000000..936b7b8c --- /dev/null +++ b/packages/core.gbapp/dialogs/LanguageDialog.ts @@ -0,0 +1,77 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | +| Licensed under the AGPL-3.0. | +| | +| According to our dual licensing model, this program can be used either | +| under the terms of the GNU Affero General Public License, version 3, | +| or under a proprietary license. | +| | +| The texts of the GNU Affero General Public License with an additional | +| permission and of our proprietary license can be found at and | +| in the LICENSE file you have received along with this program. | +| | +| This program is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY, without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| GNU Affero General Public License for more details. | +| | +| "General Bots" is a registered trademark of Pragmatismo.io. | +| The licensing of the program under the AGPLv3 does not imply a | +| trademark license. Therefore any rights, title and interest in | +| our trademarks remain entirely with us. | +| | +\*****************************************************************************/ + +/** + * @fileoverview General Bots server core. + */ + +'use strict'; + +import { BotAdapter } from 'botbuilder'; +import { WaterfallDialog } from 'botbuilder-dialogs'; +import { GBMinInstance, IGBDialog } from 'botlib'; +import { Messages } from '../strings'; +import { SecService } from '../../security.gbapp/services/SecService'; +import { GBServer } from '../../../src/app'; +import { GBConversationalService } from '../services/GBConversationalService'; +/** + * Dialog for the bot explains about itself. + */ +export class LanguageDialog extends IGBDialog { + /** + * Setup dialogs flows and define services call. + * + * @param bot The bot adapter. + * @param min The minimal bot instance data. + */ + public static setup(bot: BotAdapter, min: GBMinInstance) { + min.dialogs.add(new WaterfallDialog('/language', [ + + async step => { + const locale = step.context.activity.locale; + + return await min.conversationalService.prompt (min, step, + Messages[locale].which_language); + }, + async step => { + let sec = new SecService(); + let from = step.context.activity.from.id; + const botId = step.result; + const instance = await min.core.loadInstanceByBotId(botId); + await sec.updateUserInstance(from, instance.instanceId); + await min.conversationalService.sendText(min, step, `Opa, vamos lá!`); + + return await step.next(); + } + ])); + } +} diff --git a/packages/core.gbapp/services/GBConfigService.ts b/packages/core.gbapp/services/GBConfigService.ts index 99256ea5..90492b92 100644 --- a/packages/core.gbapp/services/GBConfigService.ts +++ b/packages/core.gbapp/services/GBConfigService.ts @@ -134,11 +134,26 @@ export class GBConfigService { value = undefined; break; case 'DISABLE_WEB': - value = "false"; + value = 'false'; break; case 'STORAGE_ACQUIRE_TIMEOUT': value = 40000; break; + case 'LOCALE': + value = 'en'; + break; + case 'LANGUAGE_DETECTOR': + value = false; + break; + case 'DEFAULT_USER_LANGUAGE': + value = 'en'; + break; + case 'DEFAULT_CONTENT_LANGUAGE': + value = 'en'; + break; + case 'ENABLE_SPELLING_CHECKER': + value = false; + break; default: GBLog.warn(`Invalid key on .env file: '${key}'`); break; diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index 25e60b4a..cc032499 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -44,12 +44,12 @@ import { Readable } from 'stream'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { SecService } from '../../security.gbapp/services/SecService'; import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService'; -import { CollectionUtil } from 'pragmatismo-io-framework'; -import { WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs'; import { MicrosoftAppCredentials } from 'botframework-connector'; +import { GBConfigService } from './GBConfigService'; const urlJoin = require('url-join'); const PasswordGenerator = require('strict-password-generator').default; const Nexmo = require('nexmo'); +import { CollectionUtil, AzureText } from 'pragmatismo-io-framework'; const { join } = require('path'); const shell = require('any-shell-escape'); const { exec } = require('child_process'); @@ -477,7 +477,7 @@ export class GBConversationalService { let nlp: RecognizerResult; try { - const saved = step.context.activity.text + const saved = step.context.activity.text; step.context.activity.text = text; nlp = await model.recognize(step.context); step.context.activity.text = saved; @@ -516,7 +516,9 @@ export class GBConversationalService { return false; } - GBLog.info(`NLP called: ${intent}, entities: ${nlp.entities.length}, score: ${score} > required (nlpScore): ${min.instance.nlpScore}`); + GBLog.info( + `NLP called: ${intent}, entities: ${nlp.entities.length}, score: ${score} > required (nlpScore): ${min.instance.nlpScore}` + ); try { step.activeDialog.state.options.entities = nlp.entities; @@ -533,14 +535,39 @@ export class GBConversationalService { return false; } - async translate(min: GBMinInstance, key: string, endPoint: string, text: string, language: string): Promise { + public async getLanguage(min: GBMinInstance, text: string): Promise { + return await AzureText.getLocale( + min.instance.textAnalyticsKey ? min.instance.textAnalyticsKey : min.instance.textAnalyticsKey, + min.instance.textAnalyticsEndpoint ? min.instance.textAnalyticsEndpoint : + min.instance.textAnalyticsEndpoint, + text + ); + } + + public async spellCheck(min: GBMinInstance, text: string): Promise { + const key = min.instance.spellcheckerKey ? min.instance.spellcheckerKey : min.instance.spellcheckerKey; + if (key) { + text = text.charAt(0).toUpperCase() + text.slice(1); + const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text); + if (data !== text) { + GBLog.info(`Spelling corrected (processMessageActivity): ${data}`); + text = data; + } + } + + return text; + } + + public async translate(min: GBMinInstance, text: string, language: string): Promise { const translatorEnabled = () => { if (min.instance.params) { const params = JSON.parse(min.instance.params); return params ? params['Enable Worldwide Translator'] === 'TRUE' : false; } return false; - }; // TODO: Encapsulate. + }; + const endPoint = min.instance.translatorEndpoint; + const key = min.instance.translatorKey; if (endPoint === null || !translatorEnabled() || process.env.TRANSLATOR_DISABLED === 'true') { return text; @@ -585,41 +612,34 @@ export class GBConversationalService { } public async prompt(min: GBMinInstance, step: GBDialogStep, text: string) { - const minBoot = GBServer.globals.minBoot as any; - let sec = new SecService(); const member = step.context.activity.from; const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); if (text !== null) { text = await min.conversationalService.translate( min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, text, - user.locale ? user.locale : 'en' + user.locale ? user.locale : min.core.getParam(min.instance, 'Locale', GBConfigService.get('LOCALE')) ); - GBLog.info(`Translated text(4): ${text}.`); + GBLog.info(`Translated text(prompt): ${text}.`); } return await step.prompt('textPrompt', text ? text : {}); } - public async sendText(min, step, text) { + public async sendText(min: GBMinInstance, step, text) { let sec = new SecService(); const member = step.context.activity.from; const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); if (user) { - const minBoot = GBServer.globals.minBoot as any; text = await min.conversationalService.translate( min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, text, - user.locale ? user.locale : 'en' + user.locale ? user.locale : min.core.getParam(min.instance, 'Locale', GBConfigService.get('LOCALE')) ); - GBLog.info(`Translated text(5): ${text}.`); - + GBLog.info(`Translated text(sendText): ${text}.`); + const analytics = new AnalyticsService(); const userProfile = await min.userProfile.get(step.context, {}); analytics.createMessage(min.instance.instanceId, userProfile.conversation, null, text); diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 85f5a411..eeede9dd 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -76,8 +76,6 @@ import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsServic import { WhatsappDirectLine } from '../../whatsapp.gblib/services/WhatsappDirectLine'; import fs = require('fs'); import { GuaribasConversationMessage } from '../../analytics.gblib/models'; -import { GBCoreService } from './GBCoreService'; -import { DialogClass } from './GBAPIService'; import { GBVMService } from './GBVMService'; /** @@ -163,7 +161,6 @@ export class GBMinService { activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot; let sec = new SecService(); - let user = await sec.getUserFromSystemId(id); if (user === null) { @@ -705,13 +702,13 @@ export class GBMinService { } private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) { - let message: GuaribasConversationMessage; + const user = await min.userProfile.get(context, {}); + let message: GuaribasConversationMessage; if (process.env.PRIVACY_STORE_MESSAGES === 'true') { + const analytics = new AnalyticsService(); // Adds message to the analytics layer. - const analytics = new AnalyticsService(); - const user = await min.userProfile.get(context, {}); if (user) { message = await analytics.createMessage( min.instance.instanceId, @@ -744,10 +741,9 @@ export class GBMinService { parts.splice(0, 1); let args = parts.join(' '); - if (cmdOrDialogName === '/call'){ + if (cmdOrDialogName === '/call') { await GBVMService.callVM(args, min, step, this.deployer); - } - else{ + } else { await step.beginDialog(cmdOrDialogName, { args: args }); } } else if (globalQuit(step.context.activity.locale, context.activity.text)) { @@ -767,57 +763,48 @@ export class GBMinService { ); await step.beginDialog('/publish', { confirm: true, firstTime: true }); } else { + let text = context.activity.text; + text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, ''); + + // Spells check the input text before translating. + + text = await min.conversationalService.spellCheck(min, text); + + // Detects user typed language and updates their locale profile if applies. + + let locale = min.core.getParam( + min.instance, + 'Default User Language', + GBConfigService.get('DEFAULT_USER_LANGUAGE') + ); + let detectLanguage = min.core.getParam( + min.instance, + 'Language Detector', + GBConfigService.get('LANGUAGE_DETECTOR') as any + ); + if (detectLanguage) { + locale = await min.conversationalService.getLanguage(min, text); + const systemUser = user.systemUser; + if (systemUser.locale != locale) { + let sec = new SecService(); + await sec.updateUserLocale(systemUser.userSystemId, locale); + } + } + + // Translates the input text if is turned on instance params. + + const originalText = context.text; + text = await min.conversationalService.translate(min, text, locale); + GBLog.info(`Translated text (processMessageActivity): ${text}.`); + context.activity.text = text; + context.activity.originalText = originalText; + + // If there is a dialog in course, continue to the next step. + if (step.activeDialog !== undefined) { await step.continueDialog(); } else { - let text = context.activity.text; - text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/ig, ""); - - // Spells check the input text before translating. - - const key = min.instance.spellcheckerKey ? min.instance.spellcheckerKey : min.instance.spellcheckerKey; - if (key) { - text = text.charAt(0).toUpperCase() + text.slice(1); - const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text); - if (data !== text) { - GBLog.info(`Spelling corrected: ${data}`); - text = data; - } - } - - let locale = 'en'; - if ( - process.env.TRANSLATOR_DISABLED !== 'true' || - min.core.getParam(min.instance, 'Enable Worldwide Translator') - ) { - const minBoot = GBServer.globals.minBoot as any; // TODO: Switch keys automatically to master/per bot. - locale = await AzureText.getLocale( - minBoot.instance.textAnalyticsKey ? minBoot.instance.textAnalyticsKey : minBoot.instance.textAnalyticsKey, - minBoot.instance.textAnalyticsEndpoint - ? minBoot.instance.textAnalyticsEndpoint - : minBoot.instance.textAnalyticsEndpoint, - text - ); - } - - let sec = new SecService(); - const member = step.context.activity.from; - - const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); - user.locale = locale; - await user.save(); - const minBoot = GBServer.globals.minBoot as any; - const notTranslatedQuery = text; - text = await min.conversationalService.translate( - min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, - text, - 'en' - ); - GBLog.info(`Translated text (1): ${text}.`); - - // Checks if any .gbapp will handle this answer, if not goes to kb.gbapp. + // Checks if any .gbapp will handle this answer, if not goes to standard kb.gbapp. let handled = false; await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => { @@ -825,7 +812,7 @@ export class GBMinService { await e.onExchangeData(min, 'handleAnswer', { query: text, step: step, - notTranslatedQuery: notTranslatedQuery, + notTranslatedQuery: originalText, message: message ? message['dataValues'] : null, user: user ? user['dataValues'] : null }) @@ -833,7 +820,6 @@ export class GBMinService { handled = true; } }); - if (!handled) { await step.beginDialog('/answer', { query: text, diff --git a/packages/core.gbapp/strings.ts b/packages/core.gbapp/strings.ts index 16497667..8d4ca1b1 100644 --- a/packages/core.gbapp/strings.ts +++ b/packages/core.gbapp/strings.ts @@ -10,9 +10,10 @@ export const Messages = { very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`, canceled: 'Canceled. If I can be useful, let me know how', whats_email: "What's your E-mail address?", + which_language: "Which language would you like to choose from?", validation_enter_valid_email: "Please enter a valid e-mail." , affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum)/i, - + }, 'pt-BR': { show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...', @@ -23,6 +24,7 @@ export const Messages = { very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`, canceled: 'Cancelado, avise como posso ser útil novamente.', whats_email: "Qual seu e-mail?", + which_language: "Qual idioma você gostaria de usar?", validation_enter_valid_email: "Por favor digite um email válido.", affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum)/i, diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 6708af4e..68afee9c 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -106,56 +106,12 @@ export class AskDialog extends IGBDialog { }, async step => { if (step.result) { - const translatorEnabled = () => { - if (min.instance.params) { - const params = JSON.parse(min.instance.params); - return params ? params['Enable Worldwide Translator'] === 'TRUE' : false; - } - return false; - }; // TODO: Encapsulate. - let text = step.result; - text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/ig, ""); - - let locale = 'en'; - const minBoot = GBServer.globals.minBoot as any; - if (process.env.TRANSLATOR_DISABLED !== 'true' && translatorEnabled()) { - locale = await AzureText.getLocale( - minBoot.instance.textAnalyticsKey ? minBoot.instance.textAnalyticsKey : minBoot.instance.textAnalyticsKey, - minBoot.instance.textAnalyticsEndpoint - ? minBoot.instance.textAnalyticsEndpoint - : minBoot.instance.textAnalyticsEndpoint, - text - ); - } + text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, ''); let sec = new SecService(); const member = step.context.activity.from; - - // Spells check the input text before translating. - text = text.charAt(0).toUpperCase() + text.slice(1); - const key = min.instance.spellcheckerKey ? min.instance.spellcheckerKey : min.instance.spellcheckerKey; - if (key) { - - const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text); - if (data !== text) { - GBLog.info(`Spelling corrected (3): ${data}`); - text = data; - } - } - const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); - user.locale = locale; - await user.save(); - const notTranslatedQuery = text; - text = await min.conversationalService.translate( - min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, - text, - 'en' - ); - GBLog.info(`Translated text (3): ${text}.`); let handled = false; await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => { @@ -163,7 +119,6 @@ export class AskDialog extends IGBDialog { await e.onExchangeData(min, 'handleAnswer', { query: text, step: step, - notTranslatedQuery: notTranslatedQuery, message: text, user: user ? user['dataValues'] : null }) @@ -189,106 +144,92 @@ export class AskDialog extends IGBDialog { private static getAnswerDialog(min: GBMinInstance, service: KBService) { return [ async step => { + let answer: GuaribasAnswer = null; const user = await min.userProfile.get(step.context, {}); - let text = step.options.query; - text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/ig, ""); - let sec = new SecService(); - const member = step.context.activity.from; - const userDb = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); const minBoot = GBServer.globals.minBoot as any; - // Spells check the input text before translating. - - const key = min.instance.spellcheckerKey ? minBoot.instance.spellcheckerKey : min.instance.spellcheckerKey; - if (key) { - text = text.charAt(0).toUpperCase() + text.slice(1); - const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text); - if (data !== text) { - GBLog.info(`Spelling corrected: ${data}`); - text = data; - } - } - - // Translates text before sending Search or NLP. - - text = await min.conversationalService.translate( - min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, - text, - userDb.locale ? userDb.locale : 'en' - ); - - GBLog.info(`Translated text (2): ${text}`); - + let text = step.options.query; + text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, ''); if (!text) { throw new Error(`/answer being called with no args query text.`); } const locale = step.context.activity.locale; + // Stops any content on projector. + await min.conversationalService.sendEvent(min, step, 'stop', undefined); + // Handle extra text from FAQ. + if (step.options && step.options.query) { text = step.options.query; } else if (step.options && step.options.fromFaq) { await min.conversationalService.sendText(min, step, Messages[locale].going_answer); } - const searchScore = min.instance.searchScore ? min.instance.searchScore : minBoot.instance.searchScore; // Searches KB for the first time. + + const searchScore = min.instance.searchScore ? min.instance.searchScore : minBoot.instance.searchScore; user.lastQuestion = text; await min.userProfile.set(step.context, user); const resultsA = await service.ask(min.instance, text, searchScore, user.subjects); // If there is some result, answer immediately. + if (resultsA !== undefined && resultsA.answer !== undefined) { // Saves some context info. + user.isAsking = false; user.lastQuestionId = resultsA.questionId; await min.userProfile.set(step.context, user); // Sends the answer to all outputs, including projector. - return await AskDialog.handleAnswer(service, min, step, resultsA.answer); - } else { - // Second time running Search, now with no filter. + answer = resultsA.answer; + + // If this search was restricted to some subjects... + } else if (user.subjects) { + // ...second time running Search, now with no filter. + const resultsB = await service.ask(min.instance, text, searchScore, undefined); + // If there is some result, answer immediately. - if (resultsB !== undefined && resultsB.answer !== undefined) { + if (resultsB !== undefined && resultsB.answer !== undefined) + { // Saves some context info. + const user2 = await min.userProfile.get(step.context, {}); user2.isAsking = false; user2.lastQuestionId = resultsB.questionId; await min.userProfile.set(step.context, user2); // Informs user that a broader search will be used. + if (user2.subjects.length > 0) { await min.conversationalService.sendText(min, step, Messages[locale].wider_answer); } - // TODO: Put braces in this IF statment. - - if (resultsB.answer) - // Sends the answer to all outputs, including projector. - - return await AskDialog.handleAnswer(service, min, step, resultsA.answer); - } else { - if (!(await min.conversationalService.routeNLP(step, min, text))) { - await min.conversationalService.sendText(min, step, Messages[locale].did_not_find); - - return await step.replaceDialog('/ask', { isReturning: true }); - } + answer = resultsB.answer; } } + + // Answers using Search or NLP responses. + + if (answer) { + return await AskDialog.handleAnswer(service, min, step, answer); + } else if (!(await min.conversationalService.routeNLP(step, min, text))) { + await min.conversationalService.sendText(min, step, Messages[locale].did_not_find); + return await step.replaceDialog('/ask', { isReturning: true }); + } } ]; } private static async handleAnswer(service: KBService, min: GBMinInstance, step: any, answer: GuaribasAnswer) { - const text = answer.content + const text = answer.content; if (text.endsWith('.docx')) { const mainName = GBVMService.getMethodNameFromVBSFilename(text); return await GBVMService.callVM(mainName, min, step, this.deployer); diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index 4bdc6bc2..0af1c9c5 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -205,8 +205,6 @@ export class KBService implements IGBKBService { const values = results.result.value; - - if (values && values.length > 0 && values[0]['@search.score'] >= searchScore) { const value = await this.getAnswerById(instance.instanceId, values[0].answerId); if (value !== null) { @@ -216,7 +214,6 @@ export class KBService implements IGBKBService { } } } else { - const data = await this.getAnswerByText(instance.instanceId, query); if (data) { return { answer: data.answer, questionId: data.question.questionId }; @@ -288,12 +285,11 @@ export class KBService implements IGBKBService { // Finds a valid worksheet because Excel returns empty slots // when loading worksheets collection. - + let worksheet: any; for (let t = 0; t < data._worksheets.length; t++) { worksheet = data._worksheets[t]; - if (worksheet) - { + if (worksheet) { break; } } @@ -302,7 +298,6 @@ export class KBService implements IGBKBService { GBLog.info(`Now importing ${rows.length} rows from tabular file ${filePath}...`); return asyncPromise.eachSeries(rows, async line => { - // Skips the first line. if ( @@ -313,7 +308,6 @@ export class KBService implements IGBKBService { line._cells[3] !== undefined && line._cells[4] !== undefined ) { - // Extracts values from columns in the current line. const subjectsText = line._cells[0].text; @@ -437,20 +431,18 @@ export class KBService implements IGBKBService { step: GBDialogStep, conversationalService: IGBConversationalService ) { - let sec = new SecService(); - const member = step.context.activity.from; - const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); - const minBoot = GBServer.globals.minBoot as any; + const user = await min.userProfile.get(step.context, {}); // Calls language translator. let text = await min.conversationalService.translate( min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, answer.content, - user.locale ? user.locale : 'en' + user.systemUser.locale + ? user.locale + : min.core.getParam(min.instance, 'Locale', GBConfigService.get('LOCALE')) ); + GBLog.info(`Translated text(playMarkdown): ${text}.`); // Converts from Markdown to HTML. @@ -492,18 +484,6 @@ export class KBService implements IGBKBService { html: string, answer: GuaribasAnswer ) { - let sec = new SecService(); - const member = step.context.activity.from; - const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name); - const minBoot = GBServer.globals.minBoot as any; - html = await min.conversationalService.translate( - min, - min.instance.translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey, - min.instance.translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, - html, - user.locale ? user.locale : 'pt' - ); - const locale = step.context.activity.locale; await min.conversationalService.sendText(min, step, Messages[locale].will_answer_projector); html = html.replace(/src\=\"kb\//gi, `src=\"../kb/`); diff --git a/packages/security.gbapp/services/SecService.ts b/packages/security.gbapp/services/SecService.ts index 4ca370cb..3b08d222 100644 --- a/packages/security.gbapp/services/SecService.ts +++ b/packages/security.gbapp/services/SecService.ts @@ -10,6 +10,7 @@ import { CollectionUtil } from 'pragmatismo-io-framework'; * Security service layer. */ export class SecService extends GBService { + public async importSecurityFile(localPath: string, instance: IGBInstance) { const security = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'security.json'), 'utf8')); await CollectionUtil.asyncForEach(security.groups, async group => { @@ -81,6 +82,17 @@ export class SecService extends GBService { await user.save(); } + public async updateUserLocale(userSystemId: number, locale: any) : Promise { + let user = await GuaribasUser.findOne({ + where: { + userSystemId: userSystemId + } + }); + user.locale = locale; + + return await user.save(); + } + public async updateUserInstance(userSystemId: string, instanceId: number): Promise { let user = await GuaribasUser.findOne({ where: { @@ -171,6 +183,8 @@ export class SecService extends GBService { return agentSystemId; } + + public async getUserFromSystemId(systemId: string): Promise { return await GuaribasUser.findOne({ where: { diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index 5699605e..ce467302 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -419,13 +419,12 @@ export class WhatsappDirectLine extends GBService { await this.sendFileToDevice(to, url, 'Audio', msg); } - public async sendToDeviceEx(to, msg, locale) { + private async sendToDeviceEx(to, text, locale) { const minBoot = GBServer.globals.minBoot as any; - const text = await this.min.conversationalService.translate(this.min, - this.min.instance.translatorKey ? this.min.instance.translatorKey : minBoot.instance.translatorKey, - this.min.instance.translatorEndpoint ? this.min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint, - msg, + text = await minBoot.conversationalService.translate( + minBoot, + text, locale ); await this.sendToDevice(to, text);