diff --git a/DATABASE-CHANGES.md b/DATABASE-CHANGES.md index de73683b..548ce2a6 100644 --- a/DATABASE-CHANGES.md +++ b/DATABASE-CHANGES.md @@ -29,4 +29,18 @@ ALTER TABLE [dbo].[GuaribasInstance] DROP COLUMN [authenticatorClientSecret] GO +``` + +# 1.7.8 +``` SQL +ALTER TABLE dbo.GuaribasUser ADD + locale nvarchar(5) NULL +GO + + +ALTER TABLE dbo.GuaribasInstance ADD + translatorKey nvarchar(64) NULL + translatorEndpoint nvarchar(64) NULL +GO + ``` \ No newline at end of file diff --git a/packages/core.gbapp/models/GBModel.ts b/packages/core.gbapp/models/GBModel.ts index 6b01f9ce..630c9fb3 100644 --- a/packages/core.gbapp/models/GBModel.ts +++ b/packages/core.gbapp/models/GBModel.ts @@ -96,6 +96,14 @@ export class GuaribasInstance extends Model @Column public textAnalyticsEndpoint: string; + + @Column({ type: DataType.STRING(64) }) + public translatorKey: string; + + @Column + @Column({ type: DataType.STRING(64) }) + public translatorEndpoint: string; + @Column public marketplacePassword: string; diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index d9b98f33..188d1a11 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -44,6 +44,7 @@ import { Messages } from '../strings'; import { GBServer } from '../../../src/app'; import { Readable } from 'stream' import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; +import { SecService } from '../../security.gblib/services/SecService'; const urlJoin = require('url-join'); const PasswordGenerator = require("strict-password-generator").default; const Nexmo = require('nexmo'); @@ -53,6 +54,8 @@ const { exec } = require('child_process') const fs = require('fs') const prism = require('prism-media') const sdk = require("microsoft-cognitiveservices-speech-sdk"); +const uuidv4 = require('uuid/v4'); +const request = require('request-promise-native'); export interface LanguagePickerSettings { defaultLocale?: string; @@ -513,6 +516,60 @@ export class GBConversationalService { return Promise.resolve(false); } + + async translate( + key: string, + endPoint: string, + text: string, + language: string + ): Promise { + + let options = { + method: 'POST', + baseUrl: endPoint, + url: 'translate', + qs: { + 'api-version': '3.0', + 'to': [language] + }, + headers: { + 'Ocp-Apim-Subscription-Key': key, + 'Ocp-Apim-Subscription-Region': 'westeurope', + 'Content-type': 'application/json', + 'X-ClientTraceId': uuidv4().toString() + }, + body: [{ + 'text': text + }], + json: true, + } + + try { + const results = await request(options); + + return results[0].translations[0].text; + } catch (error) { + const msg = `Error calling Translator service layer. Error is: ${error}.`; + + return Promise.reject(new Error(msg)); + } + } + + public async sendText(min, 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); + text = await min.conversationalService.translate( + min.instance.translatorKey, + min.instance.translatorEndpoint, + text, + user.locale + ); + + } + public async checkLanguage(step: GBDialogStep, min, text) { const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, min.instance.textAnalyticsEndpoint, text); if (locale !== step.context.activity.locale.split('-')[0]) { diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 88611202..23b7f010 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -44,7 +44,7 @@ const removeRoute = require('express-remove-route'); const AuthenticationContext = require('adal-node').AuthenticationContext; const wash = require('washyourmouthoutwithsoap'); import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder'; -import { CollectionUtil } from 'pragmatismo-io-framework'; +import { CollectionUtil, AzureText } from 'pragmatismo-io-framework'; import { ConfirmPrompt, WaterfallDialog } from 'botbuilder-dialogs'; import { GBDialogStep, @@ -210,7 +210,6 @@ export class GBMinService { const { min, adapter, conversationState } = await this.buildBotAdapter(instance, GBServer.globals.sysPackages); GBServer.globals.minInstances.push(min); - await this.deployer.deployPackage(min, 'packages/default.gbdialog'); await this.deployer.deployPackage(min, 'packages/default.gbtheme'); // Install per bot deployed packages. @@ -434,7 +433,6 @@ export class GBMinService { MicrosoftAppCredentials.trustServiceUrl('https://directline.botframework.com', new Date(new Date().setFullYear(new Date().getFullYear() + 10))); - // The minimal bot is built here. const min = new GBMinInstance(); @@ -646,7 +644,7 @@ export class GBMinService { // Checks for global exit kewywords cancelling any active dialogs. const globalQuit = (locale, utterance) => { - return utterance.match(Messages[locale].global_quit); + return utterance.match(Messages.global_quit); } const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined; @@ -666,7 +664,7 @@ export class GBMinService { let args = parts.join(' '); await step.beginDialog(dialogName, { args: args }); - } else if (globalQuit(step.context.activity.locale, context.activity.text)) { + } else if (globalQuit(step.context.activity.locale, context.activity.text)) { // TODO: Hard-code additional languages. await step.cancelAllDialogs(); await step.context.sendActivity(Messages[step.context.activity.locale].canceled); } else if (context.activity.text === 'admin') { @@ -680,8 +678,29 @@ export class GBMinService { if (step.activeDialog !== undefined) { await step.continueDialog(); } else { + + let query = context.activity.text; + + const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, + min.instance.textAnalyticsEndpoint, query); + + 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(); + + query = await min.conversationalService.translate( + min.instance.translatorKey, + min.instance.translatorEndpoint, + query, + 'pt'); + GBLog.info(`Translated text: ${query}.`) + await step.beginDialog('/answer', { - query: context.activity.text + query: query }); } } diff --git a/packages/core.gbapp/strings.ts b/packages/core.gbapp/strings.ts index 178ea7a8..0488a15b 100644 --- a/packages/core.gbapp/strings.ts +++ b/packages/core.gbapp/strings.ts @@ -1,5 +1,6 @@ export const Messages = { + global_quit: /^(sair|sai|chega|exit|quit|finish|end|ausfahrt|verlassen)/i, 'en-US': { show_video: 'I will show you a video, please wait...', good_morning: 'good morning', @@ -7,7 +8,6 @@ export const Messages = { good_night: 'good night', hi: (msg) => `Hello, ${msg}.`, very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`, - global_quit: /^(quit|Quit)/i, canceled: 'Canceled. If I can be useful, let me know how', whats_email: "What's your E-mail address?", validation_enter_valid_email: "Please enter a valid e-mail." @@ -19,7 +19,6 @@ export const Messages = { good_night: 'boa noite', hi: (msg) => `Oi, ${msg}.`, very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`, - global_quit: /^(sair|Sair)/i, canceled: 'Cancelado, avise como posso ser útil novamente.', whats_email: "Qual seu e-mail?", validation_enter_valid_email: "Por favor digite um email válido." diff --git a/packages/default.gbdialog/translator.gbignore b/packages/default.gbdialog/translator.gbignore new file mode 100644 index 00000000..bbd32df1 --- /dev/null +++ b/packages/default.gbdialog/translator.gbignore @@ -0,0 +1,13 @@ +rem hi + +talk "Qual seu nome?" +hear name + +talk "Qual seu CPF?" +hear CPF + +talk "Por que você abrirá este chamado?" +hear translated motivo + +talk "Seu nome: " + name +talk "Você disse em Português " + motivo. \ No newline at end of file diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index e6fa00d6..41631112 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -44,6 +44,7 @@ import { Messages } from '../strings'; import { KBService } from './../services/KBService'; import { GuaribasAnswer } from '../models'; import { GBMinService } from '../../../packages/core.gbapp/services/GBMinService'; +import { SecService } from '../../security.gblib/services/SecService'; /** * Dialog arguments. @@ -98,7 +99,9 @@ export class AskDialog extends IGBDialog { }, async step => { if (step.result) { - return await step.replaceDialog('/answer', { query: step.result }); + + let query = step.result; + return await step.replaceDialog('/answer', { query: query }); } else { return await step.next(); } diff --git a/packages/security.gblib/models/index.ts b/packages/security.gblib/models/index.ts index f9324d78..d11d0e2a 100644 --- a/packages/security.gblib/models/index.ts +++ b/packages/security.gblib/models/index.ts @@ -68,6 +68,9 @@ export class GuaribasUser extends Model { @Column public email: string; + @Column(DataType.STRING(5)) + @Column public locale: string; + @ForeignKey(() => GuaribasInstance) @Column public instanceId: number; diff --git a/packages/security.gblib/services/SecService.ts b/packages/security.gblib/services/SecService.ts index 5a64963e..29c659a5 100644 --- a/packages/security.gblib/services/SecService.ts +++ b/packages/security.gblib/services/SecService.ts @@ -11,6 +11,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 => {