diff --git a/deploy/core.gbapp/models/GBModel.ts b/deploy/core.gbapp/models/GBModel.ts index d900635c..399086f3 100644 --- a/deploy/core.gbapp/models/GBModel.ts +++ b/deploy/core.gbapp/models/GBModel.ts @@ -52,7 +52,6 @@ import { CreatedAt, UpdatedAt, DataType, - IsUUID, PrimaryKey, AutoIncrement } from "sequelize-typescript"; @@ -90,6 +89,8 @@ export class GuaribasInstance extends Model implements IGBInst @Column textAnalyticsKey: string; + @Column textAnalyticsServerUrl: string; + @Column marketplacePassword: string; @Column webchatKey: string; diff --git a/deploy/core.gbapp/services/GBConversationalService.ts b/deploy/core.gbapp/services/GBConversationalService.ts index abb5203e..6b0171e1 100644 --- a/deploy/core.gbapp/services/GBConversationalService.ts +++ b/deploy/core.gbapp/services/GBConversationalService.ts @@ -30,78 +30,77 @@ | | \*****************************************************************************/ - "use strict"; const logger = require("../../../src/logger"); import { GBCoreService } from "./GBCoreService"; import { IGBConversationalService } from "botlib"; -import { GBError } from "botlib"; -import { GBERROR_TYPE } from "botlib"; import { GBMinInstance } from "botlib"; import { LuisRecognizer } from "botbuilder-ai"; import { MessageFactory } from "botbuilder"; -import { resolve } from "bluebird"; + +export interface LanguagePickerSettings { + defaultLocale?: string; + supportedLocales?: string[]; +} export class GBConversationalService implements IGBConversationalService { + coreService: GBCoreService; - coreService: GBCoreService; + constructor(coreService: GBCoreService) { + this.coreService = coreService; + } - constructor(coreService: GBCoreService) { - this.coreService = coreService; - } - - async sendEvent(dc: any, name: string, value: any): Promise { - const msg = MessageFactory.text(''); - msg.value = value; - msg.type = "event"; - msg.name = name; - return dc.context.sendActivity(msg); - } - - async runNLP( - dc: any, - min: GBMinInstance, - text: string - ): Promise { - - const model = new LuisRecognizer({ - appId: min.instance.nlpAppId, - subscriptionKey: min.instance.nlpSubscriptionKey, - serviceEndpoint: min.instance.nlpServerUrl - }); - - let res = await model.recognize(dc.context); - - // Resolve intents returned from LUIS - let topIntent = LuisRecognizer.topIntent(res); - - if (topIntent) { - var intent = topIntent; - var entity = - res.entities && res.entities.length > 0 - ? res.entities[0].entity.toUpperCase() - : null; - logger.info( - "luis: intent: [" + intent + "] entity: [" + entity + "]" - ); - - try { - await dc.replace("/" + intent); - } catch (error) { - logger.info("error: intent: [" + intent + "] error: [" + error + "]"); - await dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito..."); - await dc.replace("/ask", { isReturning: true }); - } - - return Promise.resolve({ intent, entities: res.entities }); - - } else { - await dc.context.sendActivity("Lamento, não achei nada a respeito..."); - await dc.replace("/ask", { isReturning: true }); - - return Promise.resolve(null); - } + getCurrentLanguage(dc: any) { + return dc.context.activity.locale; + } + + async sendEvent(dc: any, name: string, value: any): Promise { + const msg = MessageFactory.text(""); + msg.value = value; + msg.type = "event"; + msg.name = name; + return dc.context.sendActivity(msg); + } + + async runNLP(dc: any, min: GBMinInstance, text: string): Promise { + // Invokes LUIS. + + const model = new LuisRecognizer({ + appId: min.instance.nlpAppId, + subscriptionKey: min.instance.nlpSubscriptionKey, + serviceEndpoint: min.instance.nlpServerUrl + }); + let res = await model.recognize(dc.context); + + // Resolves intents returned from LUIS. + + let topIntent = LuisRecognizer.topIntent(res); + if (topIntent) { + var intent = topIntent; + var entity = + res.entities && res.entities.length > 0 + ? res.entities[0].entity.toUpperCase() + : null; + logger.info("luis: intent: [" + intent + "] entity: [" + entity + "]"); + + try { + await dc.replace("/" + intent); + } catch (error) { + logger.info("error: intent: [" + intent + "] error: [" + error + "]"); + await dc.context.sendActivity( + "Desculpe-me, não encontrei nada a respeito..." + ); + await dc.replace("/ask", { isReturning: true }); + } + + return Promise.resolve({ intent, entities: res.entities }); + } else { + await dc.context.sendActivity("Lamento, não achei nada a respeito..."); + await dc.replace("/ask", { isReturning: true }); + + return Promise.resolve(null); } + } } diff --git a/deploy/core.gbapp/services/GBCoreService.ts b/deploy/core.gbapp/services/GBCoreService.ts index f2bd1662..e0118777 100644 --- a/deploy/core.gbapp/services/GBCoreService.ts +++ b/deploy/core.gbapp/services/GBCoreService.ts @@ -33,16 +33,15 @@ "use strict"; const logger = require("../../../src/logger"); -import { Sequelize } from 'sequelize-typescript'; +import { Sequelize } from "sequelize-typescript"; import { GBConfigService } from "./GBConfigService"; -import { IGBInstance, IGBCoreService } from 'botlib'; +import { IGBInstance, IGBCoreService } from "botlib"; import { GuaribasInstance } from "../models/GBModel"; /** * Core service layer. */ export class GBCoreService implements IGBCoreService { - /** * Data access layer instance. */ @@ -63,8 +62,8 @@ export class GBCoreService implements IGBCoreService { */ private changeColumnQuery: (tableName, attributes) => string; - /** - * Dialect used. Tested: mssql and sqlite. + /** + * Dialect used. Tested: mssql and sqlite. */ private dialect: string; @@ -75,212 +74,245 @@ export class GBCoreService implements IGBCoreService { this.dialect = GBConfigService.get("DATABASE_DIALECT"); } - /** - * Gets database config and connect to storage. + /** + * Gets database config and connect to storage. */ async initDatabase() { - return new Promise( - (resolve, reject) => { + return new Promise((resolve, reject) => { + try { + let host: string | undefined; + let database: string | undefined; + let username: string | undefined; + let password: string | undefined; + let storage: string | undefined; - try { + if (this.dialect === "mssql") { + host = GBConfigService.get("DATABASE_HOST"); + database = GBConfigService.get("DATABASE_NAME"); + username = GBConfigService.get("DATABASE_USERNAME"); + password = GBConfigService.get("DATABASE_PASSWORD"); + } else if (this.dialect === "sqlite") { + storage = GBConfigService.get("DATABASE_STORAGE"); + } - let host: string | undefined; - let database: string | undefined; - let username: string | undefined; - let password: string | undefined; - let storage: string | undefined; - - if (this.dialect === "mssql") { - host = GBConfigService.get("DATABASE_HOST"); - database = GBConfigService.get("DATABASE_NAME"); - username = GBConfigService.get("DATABASE_USERNAME"); - password = GBConfigService.get("DATABASE_PASSWORD"); - } else if (this.dialect === "sqlite") { - storage = GBConfigService.get("DATABASE_STORAGE"); - } - - let logging = (GBConfigService.get("DATABASE_LOGGING") === "true") - ? (str: string) => { logger.info(str); } + let logging = + GBConfigService.get("DATABASE_LOGGING") === "true" + ? (str: string) => { + logger.info(str); + } : false; - let encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true"); + let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true"; - this.sequelize = new Sequelize({ - host: host, - database: database, - username: username, - password: password, - logging: logging, - operatorsAliases: false, - dialect: this.dialect, - storage: storage, - dialectOptions: { - encrypt: encrypt - }, - pool: { - max: 32, - min: 8, - idle: 40000, - evict: 40000, - acquire: 40000 - }, - }); - - if (this.dialect === "mssql") { - this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; - this.createTableQuery = this.queryGenerator.createTableQuery; - this.queryGenerator.createTableQuery = (tableName, attributes, options) => - this.createTableQueryOverride(tableName, attributes, options); - this.changeColumnQuery = this.queryGenerator.changeColumnQuery; - this.queryGenerator.changeColumnQuery = (tableName, attributes) => - this.changeColumnQueryOverride(tableName, attributes); + this.sequelize = new Sequelize({ + host: host, + database: database, + username: username, + password: password, + logging: logging, + operatorsAliases: false, + dialect: this.dialect, + storage: storage, + dialectOptions: { + encrypt: encrypt + }, + pool: { + max: 32, + min: 8, + idle: 40000, + evict: 40000, + acquire: 40000 } - resolve(); - } catch (error) { - reject(error); + }); + + if (this.dialect === "mssql") { + this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; + this.createTableQuery = this.queryGenerator.createTableQuery; + this.queryGenerator.createTableQuery = ( + tableName, + attributes, + options + ) => this.createTableQueryOverride(tableName, attributes, options); + this.changeColumnQuery = this.queryGenerator.changeColumnQuery; + this.queryGenerator.changeColumnQuery = (tableName, attributes) => + this.changeColumnQueryOverride(tableName, attributes); } - }); + resolve(); + } catch (error) { + reject(error); + } + }); } + /** + * SQL: + * + * // let sql: string = '' + + * // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + + * // 'CREATE TABLE [UserGroup] (\n' + + * // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' + + * // ' [userId] INTEGER NULL,\n' + + * // ' [groupId] INTEGER NULL,\n' + + * // ' [instanceId] INTEGER NULL,\n' + + * // ' PRIMARY KEY ([id1], [id2]),\n' + + * // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + + * // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,\n' + + * // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION);'; + */ private createTableQueryOverride(tableName, attributes, options): string { - let sql: string = this.createTableQuery.apply(this.queryGenerator, - [tableName, attributes, options]); - // let sql: string = '' + - // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + - // 'CREATE TABLE [UserGroup] (\n' + - // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' + - // ' [userId] INTEGER NULL,\n' + - // ' [groupId] INTEGER NULL,\n' + - // ' [instanceId] INTEGER NULL,\n' + - // ' PRIMARY KEY ([id1], [id2]),\n' + - // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + - // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,\n' + - // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION);'; + let sql: string = this.createTableQuery.apply(this.queryGenerator, [ + tableName, + attributes, + options + ]); const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { const table = matches[1]; const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/; - sql = sql.replace(re2, (match: string, ...args: any[]): string => { - return 'CONSTRAINT [' + table + '_pk] ' + match; - }); + sql = sql.replace( + re2, + (match: string, ...args: any[]): string => { + return "CONSTRAINT [" + table + "_pk] " + match; + } + ); const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re4 = /\[([^\]]*)\]/g; - sql = sql.replace(re3, (match: string, ...args: any[]): string => { - const fkcols = args[0]; - let fkname = table; - let matches = re4.exec(fkcols); - while (matches != null) { - fkname += '_' + matches[1]; - matches = re4.exec(fkcols); + sql = sql.replace( + re3, + (match: string, ...args: any[]): string => { + const fkcols = args[0]; + let fkname = table; + let matches = re4.exec(fkcols); + while (matches != null) { + fkname += "_" + matches[1]; + matches = re4.exec(fkcols); + } + return "CONSTRAINT [" + fkname + "_fk] FOREIGN KEY (" + fkcols + ")"; } - return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; - }); + ); } return sql; } + + /** + * SQL: + * let sql = '' + + * 'ALTER TABLE [UserGroup]\n' + + * ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + + * ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, \n' + + * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION;\n'; + */ private changeColumnQueryOverride(tableName, attributes): string { - let sql: string = this.changeColumnQuery.apply(this.queryGenerator, - [tableName, attributes]); - // let sql = '' + - // 'ALTER TABLE [UserGroup]\n' + - // ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + - // ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, \n' + - // ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION;\n'; + let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ + tableName, + attributes + ]); const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { const table = matches[1]; const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re3 = /\[([^\]]*)\]/g; - sql = sql.replace(re2, (match: string, ...args: any[]): string => { - const fkcols = args[2]; - let fkname = table; - let matches = re3.exec(fkcols); - while (matches != null) { - fkname += '_' + matches[1]; - matches = re3.exec(fkcols); + sql = sql.replace( + re2, + (match: string, ...args: any[]): string => { + const fkcols = args[2]; + let fkname = table; + let matches = re3.exec(fkcols); + while (matches != null) { + fkname += "_" + matches[1]; + matches = re3.exec(fkcols); + } + return ( + (args[0] ? args[0] : "") + + "CONSTRAINT [" + + fkname + + "_fk] FOREIGN KEY (" + + fkcols + + ")" + ); } - return (args[0] ? args[0] : '') + 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; - }); + ); } return sql; } async syncDatabaseStructure() { - return new Promise( - (resolve, reject) => { - if (GBConfigService.get("DATABASE_SYNC") === "true") { - const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true"); - const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true"); - logger.info("Syncing database..."); - this.sequelize.sync({ + return new Promise((resolve, reject) => { + if (GBConfigService.get("DATABASE_SYNC") === "true") { + const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"; + const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"; + logger.info("Syncing database..."); + this.sequelize + .sync({ alter: alter, force: force - }).then(value => { - logger.info("Database synced."); - resolve(value); - }, err => reject(err)); - } else { - logger.info("Database synchronization is disabled."); - resolve(); - } - }); + }) + .then( + value => { + logger.info("Database synced."); + resolve(value); + }, + err => reject(err) + ); + } else { + logger.info("Database synchronization is disabled."); + resolve(); + } + }); } /** * Loads all items to start several listeners. */ async loadInstances(): Promise { - return new Promise( - (resolve, reject) => { - GuaribasInstance.findAll({}) - .then((items: IGBInstance[]) => { - if (!items) items = []; + return new Promise((resolve, reject) => { + GuaribasInstance.findAll({}) + .then((items: IGBInstance[]) => { + if (!items) items = []; - if (items.length == 0) { - resolve([]); - } else { - resolve(items); - } - }) - .catch(reason => { - if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { - resolve([]); - } else { - logger.info(`GuaribasServiceError: ${reason}`); - reject(reason); - } - }); - }); + if (items.length == 0) { + resolve([]); + } else { + resolve(items); + } + }) + .catch(reason => { + if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { + resolve([]); + } else { + logger.info(`GuaribasServiceError: ${reason}`); + reject(reason); + } + }); + }); } /** * Loads just one Bot instance. */ async loadInstance(botId: string): Promise { - return new Promise( - (resolve, reject) => { + return new Promise((resolve, reject) => { + let options = { where: {} }; - let options = { where: {} }; + if (botId != "[default]") { + options.where = { botId: botId }; + } - if (botId != "[default]") { - options.where = { botId: botId }; - } - - GuaribasInstance.findOne(options) - .then((instance: IGBInstance) => { - if (instance) { - resolve(instance); - } else { - resolve(null); - } - }) - .catch(err => { - logger.info(`GuaribasServiceError: ${err}`); - reject(err); - }); - }); + GuaribasInstance.findOne(options) + .then((instance: IGBInstance) => { + if (instance) { + resolve(instance); + } else { + resolve(null); + } + }) + .catch(err => { + logger.info(`GuaribasServiceError: ${err}`); + reject(err); + }); + }); } } diff --git a/deploy/core.gbapp/services/GBDeployer.ts b/deploy/core.gbapp/services/GBDeployer.ts index 9f3daa30..f515650d 100644 --- a/deploy/core.gbapp/services/GBDeployer.ts +++ b/deploy/core.gbapp/services/GBDeployer.ts @@ -1,4 +1,4 @@ -import { IGBPackage } from 'botlib'; +import { IGBPackage } from "botlib"; /*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | @@ -40,12 +40,12 @@ const Fs = require("fs"); const WaitUntil = require("wait-until"); const express = require("express"); -import { KBService } from './../../kb.gbapp/services/KBService'; +import { KBService } from "./../../kb.gbapp/services/KBService"; import { GBImporter } from "./GBImporter"; import { IGBCoreService, IGBInstance } from "botlib"; import { GBConfigService } from "./GBConfigService"; import { GBError } from "botlib"; -import { GuaribasPackage } from '../models/GBModel'; +import { GuaribasPackage } from "../models/GBModel"; /** Deployer service for bots, themes, ai and more. */ export class GBDeployer { @@ -62,13 +62,16 @@ export class GBDeployer { this.importer = importer; } - - /** - * - * Performs package deployment in all .gbai or default. - * + /** + * + * Performs package deployment in all .gbai or default. + * * */ - public deployPackages(core: IGBCoreService, server: any, appPackages: Array) { + public deployPackages( + core: IGBCoreService, + server: any, + appPackages: Array + ) { let _this = this; return new Promise((resolve, reject) => { try { @@ -83,31 +86,31 @@ export class GBDeployer { let generalPackages = new Array(); function doIt(path) { - const isDirectory = source => Fs.lstatSync(source).isDirectory() + const isDirectory = source => Fs.lstatSync(source).isDirectory(); const getDirectories = source => - Fs.readdirSync(source).map(name => Path.join(source, name)).filter(isDirectory) + Fs.readdirSync(source) + .map(name => Path.join(source, name)) + .filter(isDirectory); let dirs = getDirectories(path); dirs.forEach(element => { - if (element.startsWith('.')) { + if (element.startsWith(".")) { logger.info(`Ignoring ${element}...`); - } - else { - if (element.endsWith('.gbot')) { + } else { + if (element.endsWith(".gbot")) { botPackages.push(element); - } - else if (element.endsWith('.gbapp')) { + } else if (element.endsWith(".gbapp")) { gbappPackages.push(element); - } - else { + } else { generalPackages.push(element); } } }); - } - logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`); + logger.info( + `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...` + ); paths.forEach(e => { logger.info(`Looking in: ${e}...`); doIt(e); @@ -121,31 +124,32 @@ export class GBDeployer { logger.info(`Deploying app: ${e}...`); // Skips .gbapp inside deploy folder. - if (!e.startsWith('deploy')) { - import(e).then(m => { - let p = new m.Package(); - p.loadPackage(core, core.sequelize); - appPackages.push(p); - logger.info(`App (.gbapp) deployed: ${e}.`); - appPackagesProcessed++; - }).catch(err => { - logger.info(`Error deploying App (.gbapp): ${e}: ${err}`); - appPackagesProcessed++; - }); + if (!e.startsWith("deploy")) { + import(e) + .then(m => { + let p = new m.Package(); + p.loadPackage(core, core.sequelize); + appPackages.push(p); + logger.info(`App (.gbapp) deployed: ${e}.`); + appPackagesProcessed++; + }) + .catch(err => { + logger.info(`Error deploying App (.gbapp): ${e}: ${err}`); + appPackagesProcessed++; + }); } else { appPackagesProcessed++; } }); - WaitUntil() .interval(1000) .times(10) - .condition(function (cb) { + .condition(function(cb) { logger.info(`Waiting for app package deployment...`); cb(appPackagesProcessed == gbappPackages.length); }) - .done(function (result) { + .done(function(result) { logger.info(`App Package deployment done.`); core.syncDatabaseStructure(); @@ -161,39 +165,39 @@ export class GBDeployer { /** Then all remaining generalPackages are loaded. */ generalPackages.forEach(filename => { - let filenameOnly = Path.basename(filename); logger.info(`Deploying package: ${filename}...`); /** Handles apps for general bots - .gbapp must stay out of deploy folder. */ - if (Path.extname(filename) === ".gbapp" || Path.extname(filename) === ".gblib") { - - + if ( + Path.extname(filename) === ".gbapp" || + Path.extname(filename) === ".gblib" + ) { /** Themes for bots. */ - } else if (Path.extname(filename) === ".gbtheme") { server.use("/themes/" + filenameOnly, express.static(filename)); - logger.info(`Theme (.gbtheme) assets accessible at: ${"/themes/" + filenameOnly}.`); - + logger.info( + `Theme (.gbtheme) assets accessible at: ${"/themes/" + + filenameOnly}.` + ); /** Knowledge base for bots. */ - } else if (Path.extname(filename) === ".gbkb") { server.use( "/kb/" + filenameOnly + "/subjects", express.static(UrlJoin(filename, "subjects")) ); - logger.info(`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`); - } - - else if (Path.extname(filename) === ".gbui" || filename.endsWith(".git")) { + logger.info( + `KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.` + ); + } else if ( + Path.extname(filename) === ".gbui" || + filename.endsWith(".git") + ) { // Already Handled - } - - /** Unknown package format. */ - - else { + } else { + /** Unknown package format. */ let err = new Error(`Package type not handled: ${filename}.`); reject(err); } @@ -203,30 +207,30 @@ export class GBDeployer { WaitUntil() .interval(1000) .times(5) - .condition(function (cb) { + .condition(function(cb) { logger.info(`Waiting for package deployment...`); - cb(totalPackages == (generalPackages.length)); + cb(totalPackages == generalPackages.length); }) - .done(function (result) { + .done(function(result) { if (botPackages.length === 0) { - logger.info(`The bot server is running empty: No bot instances have been found, at least one .gbot file must be deployed.`); - } - else { + logger.info( + "The server is running with no bot instances, at least one .gbot file must be deployed." + ); + } else { logger.info(`Package deployment done.`); } resolve(); }); }); - } catch (err) { logger.error(err); - reject(err) + reject(err); } }); } - /** - * Deploys a bot to the storage. + /** + * Deploys a bot to the storage. */ async deployBot(localPath: string): Promise { @@ -241,19 +245,18 @@ export class GBDeployer { async deployPackageToStorage( instanceId: number, - packageName: string): Promise { + packageName: string + ): Promise { return GuaribasPackage.create({ packageName: packageName, instanceId: instanceId }); - } deployTheme(localPath: string) { // DISABLED: Until completed, "/ui/public". // FsExtra.copy(localPath, this.workDir + packageName) // .then(() => { - // }) // .catch(err => { // var gberr = GBError.create( @@ -263,7 +266,6 @@ export class GBDeployer { } async deployPackageFromLocalPath(localPath: string) { - let packageType = Path.extname(localPath); switch (packageType) { @@ -279,7 +281,6 @@ export class GBDeployer { return service.deployKb(this.core, this, localPath); case ".gbui": - break; default: @@ -291,11 +292,7 @@ export class GBDeployer { } } - async undeployPackageFromLocalPath( - instance: IGBInstance, - localPath: string - - ) { + async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) { let packageType = Path.extname(localPath); let packageName = Path.basename(localPath); @@ -315,7 +312,6 @@ export class GBDeployer { return service.undeployKbFromStorage(instance, p.packageId); case ".gbui": - break; default: @@ -327,8 +323,10 @@ export class GBDeployer { } } - async getPackageByName(instanceId: number, packageName: string): - Promise { + async getPackageByName( + instanceId: number, + packageName: string + ): Promise { var where = { packageName: packageName, instanceId: instanceId }; return GuaribasPackage.findOne({ where: where @@ -341,7 +339,6 @@ export class GBDeployer { * */ async scanBootPackage() { - const deployFolder = "deploy"; let bootPackage = GBConfigService.get("BOOT_PACKAGE"); diff --git a/deploy/core.gbapp/services/GBMinService.ts b/deploy/core.gbapp/services/GBMinService.ts index c11151c5..568ce9f8 100644 --- a/deploy/core.gbapp/services/GBMinService.ts +++ b/deploy/core.gbapp/services/GBMinService.ts @@ -32,42 +32,44 @@ "use strict"; - const { TextPrompt } = require("botbuilder-dialogs"); const UrlJoin = require("url-join"); const express = require("express"); const logger = require("../../../src/logger"); -import { BotFrameworkAdapter, BotStateSet, ConversationState, MemoryStorage, UserState } from "botbuilder"; -import { LanguageTranslator, LocaleConverter } from "botbuilder-ai"; +import { + BotFrameworkAdapter, + BotStateSet, + ConversationState, + MemoryStorage, + UserState +} from "botbuilder"; import { GBCoreService } from "./GBCoreService"; import { GBConversationalService } from "./GBConversationalService"; -import { GBConfigService } from "./GBConfigService"; import * as request from "request-promise-native"; -import { GBMinInstance, IGBCoreService, IGBInstance, IGBPackage, GBError } from "botlib"; -import { GBServiceCallback } from "botlib"; +import { + GBMinInstance, + IGBPackage, +} from "botlib"; import { GBAnalyticsPackage } from "../../analytics.gblib"; import { GBCorePackage } from "../../core.gbapp"; -import { GBKBPackage } from '../../kb.gbapp'; -import { GBDeployer } from './GBDeployer'; -import { GBSecurityPackage } from '../../security.gblib'; -import { GBAdminPackage } from './../../admin.gbapp/index'; +import { GBKBPackage } from "../../kb.gbapp"; +import { GBDeployer } from "./GBDeployer"; +import { GBSecurityPackage } from "../../security.gblib"; +import { GBAdminPackage } from "./../../admin.gbapp/index"; import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp"; import { GBWhatsappPackage } from "../../whatsapp.gblib"; /** Minimal service layer for a bot. */ export class GBMinService { - core: GBCoreService; conversationalService: GBConversationalService; deployer: GBDeployer; - corePackage = "core.gbai"; - /** * Static initialization of minimal instance. * @@ -83,19 +85,21 @@ export class GBMinService { this.deployer = deployer; } - /** - * - * Constructs a new minimal instance for each bot. - * + /** + * + * Constructs a new minimal instance for each bot. + * * @param server An HTTP server. * @param appPackages List of loaded .gbapp associated with this instance. - * + * * @return Loaded minimal bot instance. - * + * * */ - async buildMin(server: any, appPackages: Array): Promise { - + async buildMin( + server: any, + appPackages: Array + ): Promise { // Serves default UI on root address '/'. let uiPackage = "default.gbui"; @@ -104,94 +108,101 @@ export class GBMinService { express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) ); - // Loads all bot instances from storage and starting loading them. + // Loads all bot instances from storage and starting loading them. let instances = await this.core.loadInstances(); - Promise.all(instances.map(async instance => { + Promise.all( + instances.map(async instance => { + // Gets the authorization key for each instance from Bot Service. - // Gets the authorization key for each instance from Bot Service. + let webchatToken = await this.getWebchatToken(instance); - let webchatToken = await this.getWebchatToken(instance); + // Serves the bot information object via HTTP so clients can get + // instance information stored on server. - // Serves the bot information object via HTTP so clients can get - // instance information stored on server. + server.get("/instances/:botId", (req, res) => { + (async () => { + // Returns the instance object to clients requesting bot info. - server.get("/instances/:botId", (req, res) => { - (async () => { + let botId = req.params.botId; + let instance = await this.core.loadInstance(botId); + if (instance) { + let speechToken = await this.getSTSToken(instance); - // Returns the instance object to clients requesting bot info. + res.send( + JSON.stringify({ + instanceId: instance.instanceId, + botId: botId, + theme: instance.theme, + secret: instance.webchatKey, // TODO: Use token. + speechToken: speechToken, + conversationId: webchatToken.conversationId + }) + ); + } else { + let error = `Instance not found: ${botId}.`; + res.sendStatus(error); + logger.error(error); + } + })(); + }); - let botId = req.params.botId; - let instance = await this.core.loadInstance(botId); - if (instance) { + // Build bot adapter. - let speechToken = await this.getSTSToken(instance); + var { min, adapter, conversationState } = await this.buildBotAdapter( + instance + ); - res.send( - JSON.stringify({ - instanceId: instance.instanceId, - botId: botId, - theme: instance.theme, - secret: instance.webchatKey, // TODO: Use token. - speechToken: speechToken, - conversationId: webchatToken.conversationId - }) - ); - } else { - let error = `Instance not found: ${botId}.`; - res.sendStatus(error); - logger.error(error); - } - })() - }); + // Call the loadBot context.activity for all packages. - // Build bot adapter. + this.invokeLoadBot(appPackages, min, server); - var { min, adapter, conversationState } = await this.buildBotAdapter(instance); + // Serves individual URL for each bot conversational interface... - // Call the loadBot context.activity for all packages. + let url = `/api/messages/${instance.botId}`; + server.post(url, async (req, res) => { + return this.receiver( + adapter, + req, + res, + conversationState, + min, + instance, + appPackages + ); + }); + logger.info( + `GeneralBots(${instance.engineName}) listening on: ${url}.` + ); - this.invokeLoadBot(appPackages, min, server); + // Serves individual URL for each bot user interface. - // Serves individual URL for each bot conversational interface... - - let url = `/api/messages/${instance.botId}`; - server.post(url, async (req, res) => { - return this.receiver(adapter, req, res, conversationState, min, - instance, appPackages); - }); - logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.` ); - - // Serves individual URL for each bot user interface. - - let uiUrl = `/${instance.botId}`; - server.use( - uiUrl, - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) - ); - logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`); - - // Setups handlers. - // send: function (context.activity, next) { - // logger.info( - // `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation}, - // Type: ${context.activity.type} `); - // this.core.createMessage( - // this.min.conversation, - // this.min.conversation.startedBy, - // context.activity.source, - // (data, err) => { - // logger.info(context.activity.source); - // } - // ); - // next(); - - })); + let uiUrl = `/${instance.botId}`; + server.use( + uiUrl, + express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) + ); + logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`); + // Setups handlers. + // send: function (context.activity, next) { + // logger.info( + // `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation}, + // Type: ${context.activity.type} `); + // this.core.createMessage( + // this.min.conversation, + // this.min.conversation.startedBy, + // context.activity.source, + // (data, err) => { + // logger.info(context.activity.source); + // } + // ); + // next(); + }) + ); } private async buildBotAdapter(instance: any) { - let adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, appPassword: instance.marketplacePassword @@ -203,7 +214,7 @@ export class GBMinService { adapter.use(new BotStateSet(conversationState, userState)); // The minimal bot is built here. - + let min = new GBMinInstance(); min.botId = instance.botId; min.bot = adapter; @@ -211,27 +222,34 @@ export class GBMinService { min.core = this.core; min.conversationalService = this.conversationalService; min.instance = await this.core.loadInstance(min.botId); - min.dialogs.add('textPrompt', new TextPrompt()); - + min.dialogs.add("textPrompt", new TextPrompt()); + return { min, adapter, conversationState }; } private invokeLoadBot(appPackages: any[], min: any, server: any) { appPackages.forEach(e => { e.sysPackages = new Array(); - [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, - GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { - logger.info(`Loading sys package: ${sysPackage.name}...`); - let p = Object.create(sysPackage.prototype) as IGBPackage; - p.loadBot(min); - e.sysPackages.push(p); - if (sysPackage.name === "GBWhatsappPackage") { - let url = "/instances/:botId/whatsapp"; - server.post(url, (req, res) => { - p["channel"].received(req, res); - }); - } - }, this); + [ + GBAdminPackage, + GBAnalyticsPackage, + GBCorePackage, + GBSecurityPackage, + GBKBPackage, + GBCustomerSatisfactionPackage, + GBWhatsappPackage + ].forEach(sysPackage => { + logger.info(`Loading sys package: ${sysPackage.name}...`); + let p = Object.create(sysPackage.prototype) as IGBPackage; + p.loadBot(min); + e.sysPackages.push(p); + if (sysPackage.name === "GBWhatsappPackage") { + let url = "/instances/:botId/whatsapp"; + server.post(url, (req, res) => { + p["channel"].received(req, res); + }); + } + }, this); e.loadBot(min); }, this); } @@ -239,11 +257,16 @@ export class GBMinService { /** * Bot Service hook method. */ - private receiver(adapter: BotFrameworkAdapter, req: any, res: any, conversationState - : ConversationState, min: any, instance: any, appPackages: any[]) { - - return adapter.processActivity(req, res, async (context) => { - + private receiver( + adapter: BotFrameworkAdapter, + req: any, + res: any, + conversationState: ConversationState, + min: any, + instance: any, + appPackages: any[] + ) { + return adapter.processActivity(req, res, async context => { const state = conversationState.get(context); const dc = min.dialogs.createContext(context, state); const user = min.userState.get(dc.context); @@ -252,86 +275,76 @@ export class GBMinService { instanceId: instance.instanceId, botId: instance.botId, theme: instance.theme, - secret: instance.webchatKey, + secret: instance.webchatKey }); user.loaded = true; user.subjects = []; } - logger.info(`[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId}, + logger.info(`[RCV]: ${context.activity.type}, ChannelID: ${ + context.activity.channelId + }, ConversationID: ${context.activity.conversation.id}, - Name: ${context.activity.name}, Text: ${context.activity.text}.`); - if (context.activity.type === "conversationUpdate" && - context.activity.membersAdded.length > 0) { + Name: ${context.activity.name}, Text: ${ + context.activity.text + }.`); + if ( + context.activity.type === "conversationUpdate" && + context.activity.membersAdded.length > 0 + ) { let member = context.activity.membersAdded[0]; if (member.name === "GeneralBots") { logger.info(`Bot added to conversation, starting chat...`); appPackages.forEach(e => { e.onNewSession(min, dc); }); - await dc.begin('/'); - } - else { + await dc.begin("/"); + } else { logger.info(`Member added to conversation: ${member.name}`); } - } - else if (context.activity.type === 'message') { - + } else if (context.activity.type === "message") { // Check to see if anyone replied. If not then start echo dialog - + if (context.activity.text === "admin") { await dc.begin("/admin"); - } - else { + } else { await dc.continue(); } - - } - else if (context.activity.type === 'event') { - + } else if (context.activity.type === "event") { if (context.activity.name === "whoAmI") { await dc.begin("/whoAmI"); - } - else if (context.activity.name === "showSubjects") { + } else if (context.activity.name === "showSubjects") { await dc.begin("/menu"); - } - else if (context.activity.name === "giveFeedback") { + } else if (context.activity.name === "giveFeedback") { await dc.begin("/feedback", { fromMenu: true }); - } - else if (context.activity.name === "showFAQ") { + } else if (context.activity.name === "showFAQ") { await dc.begin("/faq"); - } - else if (context.activity.name === "ask") { + } else if (context.activity.name === "ask") { await dc.begin("/answer", { query: (context.activity as any).data, fromFaq: true }); - } - else if (context.activity.name === "quality") { + } else if (context.activity.name === "quality") { await dc.begin("/quality", { // TODO: score: context.activity.data }); - } - else { + } else { await dc.continue(); } } }); } - /** * Get Webchat key from Bot Service. - * + * * @param instance The Bot instance. - * + * */ async getWebchatToken(instance: any) { - let options = { - url: - "https://directline.botframework.com/v3/directline/tokens/generate", + url: "https://directline.botframework.com/v3/directline/tokens/generate", method: "POST", headers: { Authorization: `Bearer ${instance.webchatKey}` @@ -350,17 +363,15 @@ export class GBMinService { /** * Gets a Speech to Text / Text to Speech token from the provider. - * + * * @param instance The general bot instance. - * + * */ async getSTSToken(instance: any) { - // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 let options = { - url: - "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken", + url: "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken", method: "POST", headers: { "Ocp-Apim-Subscription-Key": instance.speechKey @@ -375,4 +386,4 @@ export class GBMinService { return Promise.reject(msg); } } -} \ No newline at end of file +} diff --git a/deploy/core.gbapp/test/example.test.ts b/deploy/core.gbapp/test/example.test.ts deleted file mode 100644 index b4dd82dd..00000000 --- a/deploy/core.gbapp/test/example.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*****************************************************************************\ -| ( )_ _ | -| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | -| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | -| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | -| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | -| | | ( )_) | | -| (_) \___/' | -| | -| 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. | -| | -\*****************************************************************************/ - -"use strict"; - -const assert = require('assert'); - -describe('Array', () => { - describe('#indexOf()', () => { - - it('should return -1 when the value is not present',()=> { - assert.equal([1,2,3].indexOf(4), -1); - }); - - }); -}); \ No newline at end of file diff --git a/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts b/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts index 33d1056e..01a26bef 100644 --- a/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts +++ b/deploy/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts @@ -19,7 +19,7 @@ | 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 | +| 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. | | | @@ -30,14 +30,13 @@ | | \*****************************************************************************/ -"use strict"; +"use strict" - -import { CSService } from '../services/CSService'; -import { AzureText } from "pragmatismo-io-framework"; -import { GBMinInstance } from "botlib"; -import { IGBDialog } from "botlib"; -import { BotAdapter } from 'botbuilder'; +import { CSService } from '../services/CSService' +import { AzureText } from "pragmatismo-io-framework" +import { GBMinInstance } from "botlib" +import { IGBDialog } from "botlib" +import { BotAdapter } from 'botbuilder' export class FeedbackDialog extends IGBDialog { @@ -49,7 +48,7 @@ export class FeedbackDialog extends IGBDialog { */ static setup(bot: BotAdapter, min: GBMinInstance) { - const service = new CSService(); + const service = new CSService() min.dialogs.add("/feedbackNumber", [ async (dc) => { @@ -57,17 +56,17 @@ export class FeedbackDialog extends IGBDialog { "O que achou do meu atendimento, de 1 a 5?", "Qual a nota do meu atendimento?", "Como define meu atendimento numa escala de 1 a 5?" - ]; - await dc.prompt('choicePrompt', messages[0], ['1', '2', '3', '4', ' 5']); + ] + await dc.prompt('choicePrompt', messages[0], ['1', '2', '3', '4', ' 5']) }, async (dc, value) => { - let rate = value.entity; - const user = min.userState.get(dc.context); - await service.updateConversationRate(user.conversation, rate); - let messages = ["Obrigado!", "Obrigado por responder."]; - await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. + let rate = value.entity + const user = min.userState.get(dc.context) + await service.updateConversationRate(user.conversation, rate) + let messages = ["Obrigado!", "Obrigado por responder."] + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. } - ]); + ]) min.dialogs.add("/feedback", [ async (dc, args) => { @@ -75,27 +74,30 @@ export class FeedbackDialog extends IGBDialog { let messages = [ "Sugestões melhoram muito minha qualidade...", "Obrigado pela sua iniciativa de sugestão." - ]; - await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. + ] + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. } let messages = [ "O que achou do meu atendimento?", "Como foi meu atendimento?", "Gostaria de dizer algo sobre meu atendimento?" - ]; - await dc.prompt('textPrompt', messages[0]); + ] + await dc.prompt('textPrompt', messages[0]) }, async (dc, value) => { - let rate = await AzureText.getSentiment(min.instance.textAnalyticsKey, "pt-br", value); + let rate = await AzureText.getSentiment(min.instance.textAnalyticsKey, + min.instance.textAnalyticsServerUrl, + min.conversationalService.getCurrentLanguage(dc), value) + if (rate > 0) { - await dc.context.sendActivity("Bom saber que você gostou. Conte comigo."); + await dc.context.sendActivity("Bom saber que você gostou. Conte comigo.") } else { await dc.context.sendActivity( "Vamos registrar sua questão, obrigado pela sinceridade." - ); + ) } - await dc.replace('/ask', { isReturning: true }); - }]); + await dc.replace('/ask', { isReturning: true }) + }]) } } diff --git a/deploy/customer-satisfaction.gbapp/services/CSService.ts b/deploy/customer-satisfaction.gbapp/services/CSService.ts index a8d9ad3a..b32bd536 100644 --- a/deploy/customer-satisfaction.gbapp/services/CSService.ts +++ b/deploy/customer-satisfaction.gbapp/services/CSService.ts @@ -35,47 +35,33 @@ import { GuaribasConversation } from '../../analytics.gblib/models'; export class CSService { - resolveQuestionAlternate( + async resolveQuestionAlternate( instanceId: number, questionTyped: string): Promise { - return new Promise( - (resolve, reject) => { - GuaribasQuestionAlternate.findOne({ - where: { - instanceId: instanceId, - questionTyped: questionTyped - } - }).then((value: GuaribasQuestionAlternate) => { - resolve(value); - }).error(reason => reject(reason)); - }); + + return GuaribasQuestionAlternate.findOne({ + where: { + instanceId: instanceId, + questionTyped: questionTyped + } + }) } - insertQuestionAlternate( + async insertQuestionAlternate( instanceId: number, questionTyped: string, questionText: string): Promise { - return new Promise( - (resolve, reject) => { - GuaribasQuestionAlternate.create({ - questionTyped: questionTyped, - questionText: questionText - }).then(item => { - resolve(item); - }).error(reason => reject(reason)); - }); + return GuaribasQuestionAlternate.create({ + questionTyped: questionTyped, + questionText: questionText + }) } - updateConversationRate( + async updateConversationRate( conversation: GuaribasConversation, rate: number ): Promise { - return new Promise( - (resolve, reject) => { - conversation.rate = rate; - conversation.save().then((value: GuaribasConversation) => { - resolve(conversation); - }).error(reason => reject(reason)); - }); + conversation.rate = rate; + return conversation.save() } } diff --git a/deploy/kb.gbapp/dialogs/AskDialog.ts b/deploy/kb.gbapp/dialogs/AskDialog.ts index c68b6d15..1b636029 100644 --- a/deploy/kb.gbapp/dialogs/AskDialog.ts +++ b/deploy/kb.gbapp/dialogs/AskDialog.ts @@ -19,7 +19,7 @@ | 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 | +| 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. | | | @@ -30,16 +30,16 @@ | | \*****************************************************************************/ -"use strict"; +"use strict" -import { IGBDialog } from "botlib"; -import { AzureText } from "pragmatismo-io-framework"; -import { GBMinInstance } from "botlib"; -import { KBService } from './../services/KBService'; -import { BotAdapter } from "botbuilder"; -import { LuisRecognizer } from "botbuilder-ai"; +import { IGBDialog } from "botlib" +import { AzureText } from "pragmatismo-io-framework" +import { GBMinInstance } from "botlib" +import { KBService } from './../services/KBService' +import { BotAdapter } from "botbuilder" +import { LuisRecognizer } from "botbuilder-ai" -const logger = require("../../../src/logger"); +const logger = require("../../../src/logger") export class AskDialog extends IGBDialog { /** @@ -50,42 +50,67 @@ export class AskDialog extends IGBDialog { */ static setup(bot: BotAdapter, min: GBMinInstance) { - const service = new KBService(min.core.sequelize); + const service = new KBService(min.core.sequelize) const model = new LuisRecognizer({ appId: min.instance.nlpAppId, subscriptionKey: min.instance.nlpSubscriptionKey, serviceEndpoint: min.instance.nlpServerUrl - }); + }) min.dialogs.add("/answer", [ + + + async (dc, args) => { // Initialize values. - const user = min.userState.get(dc.context); - let text = args.query; + const user = min.userState.get(dc.context) + let text = args.query if (!text) { throw new Error(`/answer being called with no args.query text.`) } + let locale = await AzureText.getLocale(min.instance.textAnalyticsKey, + min.instance.textAnalyticsServerUrl, text) + if (locale != dc.context.activity.locale.split("-")[0]) + { + switch(locale) + { + case "pt": + await dc.context.sendActivity("OK, mundando de idioma para o Português..."); + dc.context.activity.locale = "pt-BR"; + break; + case "en": + await dc.context.sendActivity("OK, changing language to English..."); + dc.context.activity.locale = "en-US"; + break; + default: + await dc.context.sendActivity(`Unknown language: ${locale}`); + break; + + } + + } + // Stops any content on projector. - await min.conversationalService.sendEvent(dc, "stop", null); + await min.conversationalService.sendEvent(dc, "stop", null) // Handle extra text from FAQ. - if (args && args.query) { - text = args.query; + if (args && args.query) { + text = args.query } else if (args && args.fromFaq) { let messages = [ `Ótima escolha, procurando resposta para sua questão...`, `Pesquisando sobre o termo...`, `Aguarde, por favor, enquanto acho sua resposta...` - ]; + ] - await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. } // Spells check the input text before sending Search or NLP. @@ -93,23 +118,22 @@ export class AskDialog extends IGBDialog { if (min.instance.spellcheckerKey) { let data = await AzureText.getSpelledText( min.instance.spellcheckerKey, - text); + text) if (data != text) { - logger.info(`Spelling corrected: ${data}`); - text = data; + logger.info(`Spelling corrected: ${data}`) + text = data } } // Searches KB for the first time. - user.lastQuestion = text; + user.lastQuestion = text let resultsA = await service.ask( min.instance, text, min.instance.searchScore, - user.subjects); - + user.subjects) // If there is some result, answer immediately. @@ -117,23 +141,23 @@ export class AskDialog extends IGBDialog { // Saves some context info. - user.isAsking = false; - user.lastQuestionId = resultsA.questionId; + user.isAsking = false + user.lastQuestionId = resultsA.questionId // Sends the answer to all outputs, including projector. - await service.sendAnswer(min.conversationalService, dc, resultsA.answer); + await service.sendAnswer(min.conversationalService, dc, resultsA.answer) // Goes to ask loop, again. - await dc.replace("/ask", { isReturning: true }); + await dc.replace("/ask", { isReturning: true }) } else { // Second time running Search, now with no filter. let resultsB = await service.ask(min.instance, text, - min.instance.searchScore, null); + min.instance.searchScore, null) // If there is some result, answer immediately. @@ -141,9 +165,9 @@ export class AskDialog extends IGBDialog { // Saves some context info. - const user = min.userState.get(dc.context); - user.isAsking = false; - user.lastQuestionId = resultsB.questionId; + const user = min.userState.get(dc.context) + user.isAsking = false + user.lastQuestionId = resultsB.questionId // Informs user that a broader search will be used. @@ -151,53 +175,53 @@ export class AskDialog extends IGBDialog { let subjectText = `${KBService.getSubjectItemsSeparatedBySpaces( user.subjects - )}`; + )}` let messages = [ `Respondendo nao apenas sobre ${subjectText}... `, `Respondendo de modo mais abrangente...`, `Vou te responder de modo mais abrangente... Não apenas sobre ${subjectText}` - ]; - await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. + ] + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. } // Sends the answer to all outputs, including projector. - await service.sendAnswer(min.conversationalService, dc, resultsB.answer); - await dc.replace("/ask", { isReturning: true }); + await service.sendAnswer(min.conversationalService, dc, resultsB.answer) + await dc.replace("/ask", { isReturning: true }) } else { - let data = await min.conversationalService.runNLP(dc, min, text); + let data = await min.conversationalService.runNLP(dc, min, text) if (!data) { let messages = [ "Desculpe-me, não encontrei nada a respeito.", "Lamento... Não encontrei nada sobre isso. Vamos tentar novamente?", "Desculpe-me, não achei nada parecido. Poderia tentar escrever de outra forma?" - ]; + ] - await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. - await dc.replace("/ask", { isReturning: true }); + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. + await dc.replace("/ask", { isReturning: true }) } } } } - ]); + ]) min.dialogs.add("/ask", [ async (dc, args) => { - const user = min.userState.get(dc.context); - user.isAsking = true; + const user = min.userState.get(dc.context) + user.isAsking = true if (!user.subjects) { - user.subjects = []; + user.subjects = [] } - let text = []; + let text = [] if (user.subjects.length > 0) { text = [ `Faça sua pergunta...`, `Pode perguntar sobre o assunto em questão... `, `Qual a pergunta?` - ]; + ] } if (args && args.isReturning) { @@ -205,16 +229,16 @@ export class AskDialog extends IGBDialog { "Sobre o que mais posso ajudar?", "Então, posso ajudar em algo a mais?", "Deseja fazer outra pergunta?" - ]; + ] } if (text.length > 0) { - await dc.prompt('textPrompt', text[0]); + await dc.prompt('textPrompt', text[0]) } }, async (dc, value) => { - await dc.endAll(); - await dc.begin("/answer", { query: value }); + await dc.endAll() + await dc.begin("/answer", { query: value }) } - ]); + ]) } } diff --git a/deploy/kb.gbapp/services/KBService.ts b/deploy/kb.gbapp/services/KBService.ts index afbbb336..6db4ebae 100644 --- a/deploy/kb.gbapp/services/KBService.ts +++ b/deploy/kb.gbapp/services/KBService.ts @@ -155,13 +155,21 @@ export class KBService { let value = await this.getAnswerById( instance.instanceId, results[0].answerId) - return Promise.resolve({ answer: value, questionId: results[0].questionId }) + if (value) { + return Promise.resolve({ answer: value, questionId: results[0].questionId }) + } + else { + return Promise.resolve({ answer: null, questionId: 0 }) + } } } else { let data = await this.getAnswerByText(instance.instanceId, query) - return Promise.resolve( - { answer: data.answer, questionId: data.question.questionId } - ) + if (data) { + return Promise.resolve( + { answer: data.answer, questionId: data.question.questionId }) + } else { + return Promise.resolve({ answer: null, questionId: 0 }) + } } } catch (reason) { @@ -461,7 +469,7 @@ export class KBService { "A resposta está na tela...", "Veja a resposta na tela..." ] - + await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. var html = answer.content diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..f587357a --- /dev/null +++ b/tslint.json @@ -0,0 +1,37 @@ +{ + "defaultSeverity": "warning", + "extends": [ + "tslint:recommended", + "tslint-microsoft-contrib" + ], + "linterOptions": { + "exclude":[ + "libraries/botframework-connector/src/generated/**/*", + "libraries/botframework-schema/**/*" + ] + }, + "rulesDirectory": [ + "node_modules/tslint-microsoft-contrib" + ], + "jsRules": {}, + "rules": { + "variable-name": false, + "no-parameter-properties": false, + "no-reserved-keywords": false, + "no-unnecessary-class":false, + "function-name": false, + "no-redundant-jsdoc": false, + "no-return-await": false, + "prefer-type-cast": false, + "no-object-literal-type-assertion":false, + "no-increment-decrement":false, + "no-any":false, + "interface-name":false, + "no-this-assignment":false, + "switch-final-break":false, + "no-parameter-reassignment":false, + "export-name":false, + "no-relative-imports": false, + "max-line-length": [true,{"limit":140,"ignore-pattern":"^\\s+\\*"}] + } +}