New promises and compiling.

This commit is contained in:
Rodrigo Rodriguez 2018-09-09 14:39:37 -03:00
parent 96f78956b6
commit 0ce8d48f09
21 changed files with 1136 additions and 1191 deletions

View file

@ -19,7 +19,7 @@
| in the LICENSE file you have received along with this program. | | in the LICENSE file you have received along with this program. |
| | | |
| This program is distributed in the hope that it will be useful, | | 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 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. | | GNU Affero General Public License for more details. |
| | | |
@ -30,157 +30,140 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict"
const UrlJoin = require("url-join"); const UrlJoin = require("url-join")
import { AzureSearch } from "pragmatismo-io-framework"
import { AzureSearch } from "pragmatismo-io-framework"; import { GBMinInstance } from "botlib"
const { DialogSet, TextPrompt, NumberPrompt } = require('botbuilder-dialogs'); import { IGBDialog } from "botlib"
const { createTextPrompt, createNumberPrompt } = require('botbuilder-prompts'); import { GBDeployer } from '../../core.gbapp/services/GBDeployer'
import { GBMinInstance } from "botlib"; import { GBImporter } from '../../core.gbapp/services/GBImporter'
import { IGBDialog } from "botlib"; import { GBConfigService } from '../../core.gbapp/services/GBConfigService'
import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { KBService } from './../../kb.gbapp/services/KBService'
import { GBImporter } from '../../core.gbapp/services/GBImporter'; import { BotAdapter } from "botbuilder"
import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { reject } from "async"
import { KBService } from './../../kb.gbapp/services/KBService';
import { BotAdapter } from "botbuilder";
/**
* Dialogs for administration tasks.
*/
export class AdminDialog extends IGBDialog { export class AdminDialog extends IGBDialog {
static setup(bot: BotAdapter, min: GBMinInstance) {
let importer = new GBImporter(min.core); static async undeployPackageCommand(text: any, min: GBMinInstance, dc) {
let deployer = new GBDeployer(min.core, importer); let packageName = text.split(" ")[1]
let importer = new GBImporter(min.core)
min.dialogs.add("/admin", [ let deployer = new GBDeployer(min.core, importer)
async (dc, args) => { dc.context.sendActivity(`Undeploying package ${packageName}...`)
const prompt = "Please, authenticate:"; let data = await deployer.undeployPackageFromLocalPath(
await dc.prompt('textPrompt', prompt);
},
async (dc, value) => {
let text = value;
const user = min.userState.get(dc.context);
if (
!user.authenticated ||
text === GBConfigService.get("ADMIN_PASS")
) {
user.authenticated = true;
dc.context.sendActivity(
"Welcome to Pragmatismo.io GeneralBots Administration."
);
await dc.prompt('textPrompt', "Which task do you wanna run now?");
} else {
dc.endAll();
}
},
async (dc, value) => {
var text = value;
const user = min.userState.get(dc.context);
if (text === "quit") {
user.authenticated = false;
dc.replace("/");
} else if (text === "sync") {
min.core.syncDatabaseStructure(() => { });
dc.context.sendActivity("Sync started...");
dc.replace("/admin", {
firstRun: false
});
} else if (text.split(" ")[0] === "rebuildIndex") {
AdminDialog.rebuildIndexCommand(min, dc, () =>
dc.replace("/admin", {
firstRun: false
})
);
} else if (text.split(" ")[0] === "deployPackage") {
AdminDialog.deployPackageCommand(text, dc, deployer, min, () =>
dc.replace("/admin", {
firstRun: false
})
);
} else if (text.split(" ")[0] === "redeployPackage") {
AdminDialog.undeployPackageCommand(text, min, dc, () => {
AdminDialog.deployPackageCommand(text, dc, deployer, min, () => {
dc.context.sendActivity("Redeploy done.");
dc.replace("/admin", {
firstRun: false
});
});
});
} else if (text.split(" ")[0] === "undeployPackage") {
AdminDialog.undeployPackageCommand(text, min, dc, () =>
dc.replace("/admin", {
firstRun: false
})
);
} else if (text.split(" ")[0] === "applyPackage") {
dc.context.sendActivity("Applying in progress...");
min.core.loadInstance(text.split(" ")[1], (item, err) => {
dc.context.sendActivity("Applying done...");
dc.replace("/");
});
dc.replace("/admin", {
firstRun: false
});
} else if (text.split(" ")[0] === "rat") {
min.conversationalService.sendEvent(dc, "play", { playerType: "login", data: null });
dc.context.sendActivity("Realize login clicando no botão de login, por favor...");
}
}
])
}
static undeployPackageCommand(text: any, min: GBMinInstance, dc, cb) {
let packageName = text.split(" ")[1];
let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer);
dc.context.sendActivity(`Undeploying package ${packageName}...`);
deployer.undeployPackageFromLocalPath(
min.instance, min.instance,
UrlJoin("deploy", packageName), UrlJoin("deploy", packageName))
(data, err) => { dc.context.sendActivity(`Package ${packageName} undeployed...`)
dc.context.sendActivity(`Package ${packageName} undeployed...`);
cb();
}
);
} }
static deployPackageCommand( static async deployPackageCommand(
text: string, text: string,
dc, dc,
deployer: GBDeployer, deployer: GBDeployer,
min: GBMinInstance, min: GBMinInstance,
cb
) { ) {
let packageName = text.split(" ")[1]; let packageName = text.split(" ")[1]
dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`); dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`)
// TODO: Find packages in all possible locations. // TODO: Find packages in all possible locations.
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH")
deployer.deployPackageFromLocalPath( let data = deployer.deployPackageFromLocalPath(
UrlJoin(additionalPath, packageName), UrlJoin(additionalPath, packageName))
(data, err) => { dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`)
dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`);
}
);
} }
static rebuildIndexCommand(min: GBMinInstance, dc, cb) { static async rebuildIndexCommand(min: GBMinInstance, dc) {
let search = new AzureSearch( let search = new AzureSearch(
min.instance.searchKey, min.instance.searchKey,
min.instance.searchHost, min.instance.searchHost,
min.instance.searchIndex, min.instance.searchIndex,
min.instance.searchIndexer min.instance.searchIndexer
); )
dc.context.sendActivity("Rebuilding index..."); dc.context.sendActivity("Rebuilding index...")
search.deleteIndex((data, err) => { await search.deleteIndex()
let kbService = new KBService(); let kbService = new KBService()
search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb", (data, err) => { await search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb")
dc.context.sendActivity("Index rebuilt."); await dc.context.sendActivity("Index rebuilt.")
}); }
});
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) {
// Setup services.
let importer = new GBImporter(min.core)
let deployer = new GBDeployer(min.core, importer)
min.dialogs.add("/admin", [
async (dc, args) => {
const prompt = "Please, authenticate:"
await dc.prompt('textPrompt', prompt)
},
async (dc, value) => {
let text = value
const user = min.userState.get(dc.context)
if (
!user.authenticated ||
text === GBConfigService.get("ADMIN_PASS")
) {
user.authenticated = true
await dc.context.sendActivity(
"Welcome to Pragmatismo.io GeneralBots Administration."
)
await dc.prompt('textPrompt', "Which task do you wanna run now?")
} else {
await dc.endAll()
}
},
async (dc, value) => {
var text = value
const user = min.userState.get(dc.context)
if (text === "quit") {
user.authenticated = false
await dc.replace("/")
} else if (text === "sync") {
await min.core.syncDatabaseStructure();
await dc.context.sendActivity("Sync started...")
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "rebuildIndex") {
await AdminDialog.rebuildIndexCommand(min, dc)
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "deployPackage") {
await AdminDialog.deployPackageCommand(text, dc, deployer, min)
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "redeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc)
await AdminDialog.deployPackageCommand(text, dc, deployer, min)
await dc.context.sendActivity("Redeploy done.")
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "undeployPackage") {
await AdminDialog.undeployPackageCommand(text, min, dc)
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "applyPackage") {
await dc.context.sendActivity("Applying in progress...")
await min.core.loadInstance(text.split(" ")[1]);
await dc.context.sendActivity("Applying done...")
await dc.replace("/admin", { firstRun: false })
} else if (text.split(" ")[0] === "rat") {
await min.conversationalService.sendEvent(dc, "play", { playerType: "login", data: null })
await dc.context.sendActivity("Realize login clicando no botão de login, por favor...")
}
}
])
} }
} }

View file

@ -30,38 +30,38 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
import { GBServiceCallback } from "botlib";
import { GuaribasUser } from "../../security.gblib/models"; import { GuaribasUser } from "../../security.gblib/models";
import { GuaribasConversation, GuaribasConversationMessage } from "../models"; import { GuaribasConversation, GuaribasConversationMessage } from "../models";
export class AnalyticsService { export class AnalyticsService {
async createConversation(
user: GuaribasUser
createConversation( ): Promise<GuaribasConversation> {
user: GuaribasUser, return new Promise<GuaribasConversation>(
cb: GBServiceCallback<GuaribasConversation> (resolve, reject) => {
) { let conversation = new GuaribasConversation();
let conversation = new GuaribasConversation(); conversation.startedBy = user;
conversation.startedBy = user; conversation.startedByUserId = user.userId;
conversation.startedByUserId = user.userId; conversation.save().then((value: GuaribasConversation) => {
conversation.save().then((value: GuaribasConversation) => { resolve(value);
cb(conversation, null); });
}); });
} }
createMessage( createMessage(
conversation: GuaribasConversation, conversation: GuaribasConversation,
user: GuaribasUser, user: GuaribasUser,
content: string, content: string
cb: GBServiceCallback<GuaribasConversationMessage> ): Promise<GuaribasConversationMessage> {
) { return new Promise<GuaribasConversationMessage>(
let message = GuaribasConversationMessage.build(); (resolve, reject) => {
message.conversation = conversation; let message = GuaribasConversationMessage.build();
message.user = user; message.conversation = conversation;
message.content = content; message.user = user;
message.save().then((value: GuaribasConversationMessage) => { message.content = content;
cb(value, null); message.save().then((value: GuaribasConversationMessage) => {
}); resolve(value);
});
});
} }
} }

View file

@ -40,9 +40,7 @@ const Walk = require("fs-walk");
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
const Swagger = require('swagger-client'); const Swagger = require('swagger-client');
const rp = require('request-promise'); const rp = require('request-promise');
import * as request from "request-promise-native"; import { GBService } from "botlib";
import { GBServiceCallback, GBService, IGBInstance } from "botlib";
export class ConsoleDirectLine extends GBService { export class ConsoleDirectLine extends GBService {
@ -193,7 +191,4 @@ export class ConsoleDirectLine extends GBService {
console.log('*' + contentLine(attachment.content.text) + '*'); console.log('*' + contentLine(attachment.content.text) + '*');
console.log('*'.repeat(width + 1) + '/'); console.log('*'.repeat(width + 1) + '/');
} }
} }

View file

@ -40,6 +40,12 @@ import { GBMinInstance } from "botlib";
import { BotAdapter } from "botbuilder"; import { BotAdapter } from "botbuilder";
export class WelcomeDialog extends IGBDialog { export class WelcomeDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
min.dialogs.add("/", [ min.dialogs.add("/", [

View file

@ -40,7 +40,15 @@ import { BotAdapter } from "botbuilder";
export class WhoAmIDialog extends IGBDialog { export class WhoAmIDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
min.dialogs.add("/whoAmI", [ min.dialogs.add("/whoAmI", [
async (dc, args) => { async (dc, args) => {
dc.context.sendActivity(`${min.instance.description}`); dc.context.sendActivity(`${min.instance.description}`);

View file

@ -1,49 +1,47 @@
/*****************************************************************************\ /*****************************************************************************\
| ( )_ _ | | ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | | ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | | | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | | | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | | | | | ( )_) | |
| (_) \___/' | | (_) \___/' |
| | | |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | | General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. | | Licensed under the AGPL-3.0. |
| | | |
| According to our dual licensing model, this program can be used either | | According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, | | under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. | | or under a proprietary license. |
| | | |
| The texts of the GNU Affero General Public License with an additional | | The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and | | permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. | | in the LICENSE file you have received along with this program. |
| | | |
| This program is distributed in the hope that it will be useful, | | 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 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. | | GNU Affero General Public License for more details. |
| | | |
| "General Bots" is a registered trademark of Pragmatismo.io. | | "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a | | The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in | | trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. | | our trademarks remain entirely with us. |
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict";
const UrlJoin = require("url-join");
const gBuilder = require("botbuilder");
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { GBConfigService } from "./GBConfigService";
import { GBCoreService } from "./GBCoreService"; import { GBCoreService } from "./GBCoreService";
import { GBService, GBServiceCallback, IGBConversationalService } from "botlib"; import { IGBConversationalService } from "botlib";
import { GBError } from "botlib"; import { GBError } from "botlib";
import { GBERROR_TYPE } from "botlib"; import { GBERROR_TYPE } from "botlib";
import { GBMinInstance } from "botlib"; import { GBMinInstance } from "botlib";
import { LuisRecognizer } from "botbuilder-ai"; import { LuisRecognizer } from "botbuilder-ai";
import {MessageFactory} from "botbuilder"; import { MessageFactory } from "botbuilder";
import { resolve } from "bluebird";
export class GBConversationalService implements IGBConversationalService { export class GBConversationalService implements IGBConversationalService {
@ -64,9 +62,8 @@ export class GBConversationalService implements IGBConversationalService {
async runNLP( async runNLP(
dc: any, dc: any,
min: GBMinInstance, min: GBMinInstance,
text: string, text: string
cb: GBServiceCallback<any> ): Promise<any> {
) {
const model = new LuisRecognizer({ const model = new LuisRecognizer({
appId: min.instance.nlpAppId, appId: min.instance.nlpAppId,
@ -74,12 +71,12 @@ export class GBConversationalService implements IGBConversationalService {
serviceEndpoint: min.instance.nlpServerUrl serviceEndpoint: min.instance.nlpServerUrl
}); });
await model.recognize(dc.context).then(res => { return await model.recognize(dc.context).then(res => {
// Resolve intents returned from LUIS // Resolve intents returned from LUIS
let topIntent = LuisRecognizer.topIntent(res); let topIntent = LuisRecognizer.topIntent(res);
if (topIntent) { if (topIntent) {
var intent = topIntent; var intent = topIntent;
var entity = var entity =
res.entities && res.entities.length > 0 res.entities && res.entities.length > 0
@ -93,21 +90,21 @@ export class GBConversationalService implements IGBConversationalService {
dc.replace("/" + intent); dc.replace("/" + intent);
} catch (error) { } catch (error) {
logger.info("error: intent: [" + intent + "] error: [" + error + "]"); logger.info("error: intent: [" + intent + "] error: [" + error + "]");
dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito..."); dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito...");
dc.replace("/ask", { isReturning: true }); dc.replace("/ask", { isReturning: true });
} }
cb({ intent, entities: res.entities }, null); return Promise.resolve({ intent, entities: res.entities });
} else { } else {
dc.context.sendActivity("Lamento, não achei nada a respeito..."); dc.context.sendActivity("Lamento, não achei nada a respeito...");
dc.replace("/ask", { isReturning: true }); dc.replace("/ask", { isReturning: true });
cb(null, null); resolve(null);
} }
} }
).catch(err => { ).catch(err => {
cb(null, new GBError(err, GBERROR_TYPE.nlpGeneralError)); return Promise.reject(new GBError(err, GBERROR_TYPE.nlpGeneralError));
}); });
} }
} }

View file

@ -32,21 +32,10 @@
"use strict"; "use strict";
const Path = require("path");
const Fs = require("fs");
const _ = require("lodash");
const Parse = require("csv-parse");
const Async = require("async");
const UrlJoin = require("url-join");
const Walk = require("fs-walk");
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { Sequelize } from 'sequelize-typescript'; import { Sequelize } from 'sequelize-typescript';
import { Promise } from "bluebird";
import { GBConfigService } from "./GBConfigService"; import { GBConfigService } from "./GBConfigService";
import { DataTypeUUIDv1 } from "sequelize"; import { IGBInstance, IGBCoreService } from 'botlib';
import { GBServiceCallback, IGBInstance, IGBCoreService } from 'botlib';
import { GuaribasInstance } from "../models/GBModel"; import { GuaribasInstance } from "../models/GBModel";
/** /**
@ -54,76 +43,103 @@ import { GuaribasInstance } from "../models/GBModel";
*/ */
export class GBCoreService implements IGBCoreService { export class GBCoreService implements IGBCoreService {
/**
* Data access layer instance.
*/
public sequelize: Sequelize; public sequelize: Sequelize;
/**
* Allows filtering on SQL generated before send to the database.
*/
private queryGenerator: any; private queryGenerator: any;
/**
* Custom create table query.
*/
private createTableQuery: (tableName, attributes, options) => string; private createTableQuery: (tableName, attributes, options) => string;
/**
* Custom change column query.
*/
private changeColumnQuery: (tableName, attributes) => string; private changeColumnQuery: (tableName, attributes) => string;
/** Dialect used. Tested: mssql and sqlite. */ /**
* Dialect used. Tested: mssql and sqlite.
*/
private dialect: string; private dialect: string;
/**
* Constructor retrieves default values.
*/
constructor() { constructor() {
this.dialect = GBConfigService.get("DATABASE_DIALECT"); this.dialect = GBConfigService.get("DATABASE_DIALECT");
} }
/** Get config and connect to storage. */ /**
initDatabase(cb) { * Gets database config and connect to storage.
*/
async initDatabase() {
return new Promise(
(resolve, reject) => {
let host: string | undefined; try {
let database: string | undefined;
let username: string | undefined;
let password: string | undefined;
let storage: string | undefined;
if (this.dialect === "mssql") { let host: string | undefined;
host = GBConfigService.get("DATABASE_HOST"); let database: string | undefined;
database = GBConfigService.get("DATABASE_NAME"); let username: string | undefined;
username = GBConfigService.get("DATABASE_USERNAME"); let password: string | undefined;
password = GBConfigService.get("DATABASE_PASSWORD"); let storage: string | undefined;
} else if (this.dialect === "sqlite") {
storage = GBConfigService.get("DATABASE_STORAGE");
}
let logging = (GBConfigService.get("DATABASE_LOGGING") === "true") if (this.dialect === "mssql") {
? (str: string) => { logger.info(str); } host = GBConfigService.get("DATABASE_HOST");
: false; 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 encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true"); let logging = (GBConfigService.get("DATABASE_LOGGING") === "true")
? (str: string) => { logger.info(str); }
: false;
this.sequelize = new Sequelize({ let encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true");
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.sequelize = new Sequelize({
this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; host: host,
this.createTableQuery = this.queryGenerator.createTableQuery; database: database,
this.queryGenerator.createTableQuery = (tableName, attributes, options) => username: username,
this.createTableQueryOverride(tableName, attributes, options); password: password,
this.changeColumnQuery = this.queryGenerator.changeColumnQuery; logging: logging,
this.queryGenerator.changeColumnQuery = (tableName, attributes) => operatorsAliases: false,
this.changeColumnQueryOverride(tableName, attributes); dialect: this.dialect,
} storage: storage,
dialectOptions: {
encrypt: encrypt
},
pool: {
max: 32,
min: 8,
idle: 40000,
evict: 40000,
acquire: 40000
},
});
setImmediate(cb); 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);
}
});
} }
private createTableQueryOverride(tableName, attributes, options): string { private createTableQueryOverride(tableName, attributes, options): string {
@ -192,70 +208,79 @@ export class GBCoreService implements IGBCoreService {
return sql; return sql;
} }
syncDatabaseStructure(cb) { async syncDatabaseStructure() {
if (GBConfigService.get("DATABASE_SYNC") === "true") { return new Promise(
const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true"); (resolve, reject) => {
const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true"); if (GBConfigService.get("DATABASE_SYNC") === "true") {
logger.info("Syncing database..."); const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true");
this.sequelize.sync({ const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true");
alter: alter, logger.info("Syncing database...");
force: force this.sequelize.sync({
}).then(value => { alter: alter,
logger.info("Database synced."); force: force
cb(); }).then(value => {
}, err => logger.error(err)); logger.info("Database synced.");
} else { resolve(value);
logger.info("Database synchronization is disabled."); }, err => reject(err));
cb(); } else {
} logger.info("Database synchronization is disabled.");
resolve();
}
});
} }
/** /**
* Loads all items to start several listeners. * Loads all items to start several listeners.
* @param cb Instances loaded or error info.
*/ */
loadInstances(cb: GBServiceCallback<IGBInstance[]>) { async loadInstances(): Promise<IGBInstance> {
GuaribasInstance.findAll({}) return new Promise(
.then((items: IGBInstance[]) => { (resolve, reject) => {
if (!items) items = []; GuaribasInstance.findAll({})
.then((items: IGBInstance[]) => {
if (!items) items = [];
if (items.length == 0) { if (items.length == 0) {
cb([], null); resolve([]);
} else { } else {
cb(items, null); resolve(items);
} }
}) })
.catch(reason => { .catch(reason => {
if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { if (reason.message.indexOf("no such table: GuaribasInstance") != -1) {
cb([], null); resolve([]);
} else { } else {
logger.info(`GuaribasServiceError: ${reason}`); logger.info(`GuaribasServiceError: ${reason}`);
cb(null, reason); reject(reason);
} }
});
}); });
} }
/** /**
* Loads just one Bot instance. * Loads just one Bot instance.
*/ */
loadInstance(botId: string, cb: GBServiceCallback<IGBInstance>) { async loadInstance(botId: string): Promise<IGBInstance> {
let options = { where: {} }; return new Promise<IGBInstance>(
(resolve, reject) => {
if (botId != "[default]") { let options = { where: {} };
options.where = { botId: botId };
}
GuaribasInstance.findOne(options) if (botId != "[default]") {
.then((instance: IGBInstance) => { options.where = { botId: botId };
if (instance) {
cb(instance, null);
} else {
cb(null, null);
} }
})
.catch(err => { GuaribasInstance.findOne(options)
cb(null, err); .then((instance: IGBInstance) => {
logger.info(`GuaribasServiceError: ${err}`); if (instance) {
resolve(instance);
} else {
resolve(null);
}
})
.catch(err => {
logger.info(`GuaribasServiceError: ${err}`);
reject(err);
});
}); });
} }
} }

View file

@ -34,25 +34,14 @@
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
const Path = require("path"); const Path = require("path");
const Fs = require("fs");
const FsExtra = require("fs-extra");
const _ = require("lodash"); const _ = require("lodash");
const Async = require("async");
const UrlJoin = require("url-join"); const UrlJoin = require("url-join");
const Walk = require("fs-walk");
const WaitUntil = require("wait-until");
import { KBService } from './../../kb.gbapp/services/KBService'; import { KBService } from './../../kb.gbapp/services/KBService';
import { GBImporter } from "./GBImporter"; import { GBImporter } from "./GBImporter";
import { GBCoreService } from "./GBCoreService";
import { GBServiceCallback, IGBCoreService, IGBInstance } from "botlib"; import { GBServiceCallback, IGBCoreService, IGBInstance } from "botlib";
import { Sequelize } from 'sequelize-typescript';
import { Promise } from "bluebird";
import { GBConfigService } from "./GBConfigService"; import { GBConfigService } from "./GBConfigService";
import { DataTypeUUIDv1 } from "sequelize"; import { GBError } from "botlib";
import { GBError, GBERROR_TYPE } from "botlib";
import { GBConversationalService } from "./GBConversationalService";
import { GuaribasPackage } from '../models/GBModel'; import { GuaribasPackage } from '../models/GBModel';
/** Deployer service for bots, themes, ai and more. */ /** Deployer service for bots, themes, ai and more. */
@ -69,156 +58,131 @@ export class GBDeployer {
this.importer = importer; this.importer = importer;
} }
/** Deploys a bot to the storage. */ /**
deployBot(localPath: string, cb: GBServiceCallback<any>) { * Deploys a bot to the storage.
*/
async deployBot(localPath: string): Promise<IGBInstance> {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); let packageName = Path.basename(localPath);
let instance = await this.importer.importIfNotExistsBotPackage(
this.importer.importIfNotExistsBotPackage(
packageName, packageName,
localPath, localPath
(data, err) => {
if (err) {
logger.info(err);
} else {
cb(data, null);
}
}
); );
return instance;
} }
deployPackageToStorage( async deployPackageToStorage(
instanceId: number, instanceId: number,
packageName: string, packageName: string): Promise<GuaribasPackage> {
cb: GBServiceCallback<GuaribasPackage> return GuaribasPackage.create({
) {
GuaribasPackage.create({
packageName: packageName, packageName: packageName,
instanceId: instanceId instanceId: instanceId
}).then((item: GuaribasPackage) => {
cb(item, null);
}); });
} }
deployTheme(localPath: string, cb: GBServiceCallback<any>) { deployTheme(localPath: string) {
// DISABLED: Until completed, "/ui/public". // DISABLED: Until completed, "/ui/public".
// FsExtra.copy(localPath, this.workDir + packageName) // FsExtra.copy(localPath, this.workDir + packageName)
// .then(() => { // .then(() => {
// cb(null, null);
// }) // })
// .catch(err => { // .catch(err => {
// var gberr = GBError.create( // var gberr = GBError.create(
// `GuaribasBusinessError: Error copying package: ${localPath}.` // `GuaribasBusinessError: Error copying package: ${localPath}.`
// ); // );
// cb(null, gberr);
// }); // });
} }
deployPackageFromLocalPath(localPath: string, cb: GBServiceCallback<any>) { async deployPackageFromLocalPath(localPath: string) {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
switch (packageType) { switch (packageType) {
case ".gbot": case ".gbot":
this.deployBot(localPath, cb); return this.deployBot(localPath);
break;
case ".gbtheme": case ".gbtheme":
this.deployTheme(localPath, cb); return this.deployTheme(localPath);
break;
// PACKAGE: Put in package logic. // PACKAGE: Put in package logic.
case ".gbkb": case ".gbkb":
let service = new KBService(); let service = new KBService();
service.deployKb(this.core, this, localPath, cb); return service.deployKb(this.core, this, localPath);
break;
case ".gbui": case ".gbui":
break; break;
default: default:
var err = GBError.create( var err = GBError.create(
`GuaribasBusinessError: Unknow package type: ${packageType}.` `GuaribasBusinessError: Unknow package type: ${packageType}.`
); );
cb(null, err); Promise.reject(err);
break; break;
} }
} }
undeployPackageFromLocalPath( async undeployPackageFromLocalPath(
instance: IGBInstance, instance: IGBInstance,
localPath: string, localPath: string
cb: GBServiceCallback<any>
) { ) {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); let packageName = Path.basename(localPath);
this.getPackageByName(instance.instanceId, packageName, (p, err) => { let p = await this.getPackageByName(instance.instanceId, packageName);
switch (packageType) {
case ".gbot":
// TODO: this.undeployBot(packageName, localPath, cb);
break;
case ".gbtheme": switch (packageType) {
// TODO: this.undeployTheme(packageName, localPath, cb); case ".gbot":
break; // TODO: this.undeployBot(packageName, localPath);
break;
case ".gbkb": case ".gbtheme":
let service = new KBService(); // TODO: this.undeployTheme(packageName, localPath);
service.undeployKbFromStorage(instance, p.packageId, cb); break;
break;
case ".gbui": case ".gbkb":
break; let service = new KBService();
return service.undeployKbFromStorage(instance, p.packageId);
default: case ".gbui":
var err = GBError.create(
`GuaribasBusinessError: Unknow package type: ${packageType}.` break;
);
cb(null, err); default:
break; var err = GBError.create(
} `GuaribasBusinessError: Unknown package type: ${packageType}.`
);
Promise.reject(err);
break;
}
}
async getPackageByName(instanceId: number, packageName: string):
Promise<GuaribasPackage> {
var where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({
where: where
}); });
} }
getPackageByName(
instanceId: number,
packageName: string,
cb: GBServiceCallback<GuaribasPackage>
) {
var where = { packageName: packageName, instanceId: instanceId };
GuaribasPackage.findOne({
where: where
})
.then((value: GuaribasPackage) => {
cb(value, null);
})
.error(reason => {
cb(null, reason);
});
}
/** /**
* *
* Hot deploy processing. * Hot deploy processing.
* *
*/ */
scanBootPackage(cb: GBServiceCallback<boolean>) { async scanBootPackage() {
const deployFolder = "deploy"; const deployFolder = "deploy";
let bootPackage = GBConfigService.get("BOOT_PACKAGE"); let bootPackage = GBConfigService.get("BOOT_PACKAGE");
if (bootPackage === "none") { if (bootPackage === "none") {
cb(true, null); return Promise.resolve(true);
} else { } else {
this.deployPackageFromLocalPath( return this.deployPackageFromLocalPath(
UrlJoin(deployFolder, bootPackage), UrlJoin(deployFolder, bootPackage)
(data, err) => {
logger.info(`Boot package deployed: ${bootPackage}`);
if (err) logger.info(err);
}
); );
} }
} }

View file

@ -33,22 +33,10 @@
"use strict"; "use strict";
const _ = require("lodash");
const Parse = require("csv-parse");
const Async = require("async");
const UrlJoin = require("url-join"); const UrlJoin = require("url-join");
const Walk = require("fs-walk");
const logger = require("../../../src/logger");
import { KBService } from './../../kb.gbapp/services/KBService';
import { Sequelize } from 'sequelize-typescript';
import { Promise } from "bluebird";
import Fs = require("fs"); import Fs = require("fs");
import Path = require("path"); import Path = require("path");
import { DataTypeUUIDv1 } from "sequelize"; import { IGBCoreService, IGBInstance } from "botlib";
import { GBConfigService } from "./GBConfigService";
import { GBCoreService } from "./GBCoreService";
import { GBServiceCallback, IGBCoreService, IGBInstance } from "botlib";
import { SecService } from "../../security.gblib/services/SecService"; import { SecService } from "../../security.gblib/services/SecService";
import { GuaribasInstance } from "../models/GBModel"; import { GuaribasInstance } from "../models/GBModel";
@ -58,12 +46,10 @@ export class GBImporter {
constructor(core: IGBCoreService) { constructor(core: IGBCoreService) {
this.core = core; this.core = core;
} }
importIfNotExistsBotPackage(
async importIfNotExistsBotPackage(
packageName: string, packageName: string,
localPath: string, localPath: string) {
cb: GBServiceCallback<IGBInstance>
) {
let _this_ = this;
let packageJson = JSON.parse( let packageJson = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8")
@ -71,20 +57,18 @@ export class GBImporter {
let botId = packageJson.botId; let botId = packageJson.botId;
this.core.loadInstance(botId, (instance, err) => { let instance = await this.core.loadInstance(botId);
if (instance) { if (instance) {
cb(instance, null); return Promise.resolve(instance);
} else { } else {
this.createInstanceInternal(packageName, localPath, packageJson, cb); return this.createInstanceInternal(packageName, localPath, packageJson);
} }
});
} }
private createInstanceInternal( private async createInstanceInternal(
packageName: string, packageName: string,
localPath: string, localPath: string,
packageJson: any, packageJson: any
cb: GBServiceCallback<IGBInstance>
) { ) {
const settings = JSON.parse( const settings = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "settings.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, "settings.json"), "utf8")
@ -97,11 +81,10 @@ export class GBImporter {
GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { GuaribasInstance.create(packageJson).then((instance: IGBInstance) => {
// PACKAGE: security.json loading
let service = new SecService(); let service = new SecService();
// TODO: service.importSecurityFile(localPath, instance); // TODO: service.importSecurityFile(localPath, instance);
cb(instance, null); Promise.resolve(instance);
}); });
} }
} }

View file

@ -77,7 +77,6 @@ export class GBMinService {
* Static initialization of minimal instance. * Static initialization of minimal instance.
* *
* @param core Basic database services to identify instance, for example. * @param core Basic database services to identify instance, for example.
* @param cb Returns the loaded instance.
*/ */
constructor( constructor(
core: GBCoreService, core: GBCoreService,
@ -91,9 +90,7 @@ export class GBMinService {
/** Constructs a new minimal instance for each bot. */ /** Constructs a new minimal instance for each bot. */
buildMin(cb: GBServiceCallback<GBMinInstance>, server: any, appPackages: Array<IGBPackage>) { async buildMin(server: any, appPackages: Array<IGBPackage>): Promise<GBMinInstance> {
var _this_ = this;
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
@ -105,246 +102,232 @@ export class GBMinService {
// Loads all bot instances from storage. // Loads all bot instances from storage.
_this_.core.loadInstances((instances: IGBInstance[], err) => { let instances = await this.core.loadInstances();
// Gets the authorization key for each instance from Bot Service. // Gets the authorization key for each instance from Bot Service.
instances.forEach(instance => { Promise.all(instances).then(async (instance: IGBInstance) => {
let options = {
url:
"https://directline.botframework.com/v3/directline/tokens/generate",
method: "POST",
headers: {
Authorization: `Bearer ${instance.webchatKey}`
}
};
request(options).then((response:
string) => {
// Serves the bot information object via http so clients can get let options = {
// instance information stored on server. url:
"https://directline.botframework.com/v3/directline/tokens/generate",
method: "POST",
headers: {
Authorization: `Bearer ${instance.webchatKey}`
}
};
let responseObject = JSON.parse(response); let response = await request(options);
server.get("/instances/:botId", (req, res) => {
// Returns the instance object to clients requesting bot info. // Serves the bot information object via http so clients can get
// instance information stored on server.
let botId = req.params.botId; let responseObject = JSON.parse(response);
_this_.core.loadInstance( server.get("/instances/:botId", (req, res) => {
botId, (async () => {
(instance: IGBInstance, err) => {
if (instance) {
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 // Returns the instance object to clients requesting bot info.
let options = { let botId = req.params.botId;
url: let instance = await this.core.loadInstance(botId);
"https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken", if (instance) {
method: "POST",
headers: {
"Ocp-Apim-Subscription-Key": instance.speechKey
}
};
request(options).then((response:
string) => {
res.send( // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
JSON.stringify({
instanceId: instance.instanceId, let options = {
botId: botId, url:
theme: instance.theme, "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
secret: instance.webchatKey, // TODO: Use token. method: "POST",
speechToken: response, headers: {
conversationId: responseObject.conversationId "Ocp-Apim-Subscription-Key": instance.speechKey
})
);
}).catch((reason) => {
let error = `Error loading Speech Service: ${reason}.`;
res.send(error);
logger.error(error);
});
} else {
let error = `Instance not found: ${botId}.`;
res.send(error);
logger.error(error);
}
} }
};
response = await request(options);
res.send(
JSON.stringify({
instanceId: instance.instanceId,
botId: botId,
theme: instance.theme,
secret: instance.webchatKey, // TODO: Use token.
speechToken: response,
conversationId: responseObject.conversationId
})
); );
}); } else {
let error = `Instance not found: ${botId}.`;
res.send(error);
logger.error(error);
}
}); });
});
// Build bot adapter. // Build bot adapter.
let adapter = new BotFrameworkAdapter({ let adapter = new BotFrameworkAdapter({
appId: instance.marketplaceId, appId: instance.marketplaceId,
appPassword: instance.marketplacePassword appPassword: instance.marketplacePassword
}); });
const storage = new MemoryStorage(); const storage = new MemoryStorage();
const conversationState = new ConversationState(storage); const conversationState = new ConversationState(storage);
const userState = new UserState(storage); const userState = new UserState(storage);
adapter.use(new BotStateSet(conversationState, userState)); adapter.use(new BotStateSet(conversationState, userState));
// The minimal bot is built here. // The minimal bot is built here.
let min = new GBMinInstance(); let min = new GBMinInstance();
min.botId = instance.botId; min.botId = instance.botId;
min.bot = adapter; min.bot = adapter;
min.userState = userState; min.userState = userState;
min.core = _this_.core; min.core = this.core;
min.conversationalService = _this_.conversationalService; min.conversationalService = this.conversationalService;
_this_.core.loadInstance(min.botId, (data, err) => { min.instance = await this.core.loadInstance(min.botId);
min.instance = data; // Call the loadBot context.activity for all packages.
// Call the loadBot context.activity for all packages. appPackages.forEach(e => {
e.sysPackages = new Array<IGBPackage>();
appPackages.forEach(e => { [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage,
e.sysPackages = new Array<IGBPackage>(); GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => {
[GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, logger.info(`Loading sys package: ${sysPackage.name}...`);
GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { let p = Object.create(sysPackage.prototype) as IGBPackage;
logger.info(`Loading sys package: ${sysPackage.name}...`); p.loadBot(min);
let p = Object.create(sysPackage.prototype) as IGBPackage; e.sysPackages.push(p);
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);
});
}
if (sysPackage.name === "GBWhatsappPackage") {
let url = "/instances/:botId/whatsapp";
server.post(url, (req, res) => {
p["channel"].received(req, res);
}); });
e.loadBot(min);
});
});
// Serves individual URL for each bot conversational interface...
let url = `/api/messages/${instance.botId}`;
logger.info(
`GeneralBots(${instance.engineName}) listening on: ${url}.`
);
min.dialogs.add('textPrompt', new TextPrompt());
server.post(`/api/messages/${instance.botId}`, (req, res) => {
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);
if (!user.loaded) {
min.conversationalService.sendEvent(
dc,
"loadInstance",
{
instanceId: instance.instanceId,
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey, // TODO: Use token.
}
);
user.loaded = true;
user.subjects = [];
} }
logger.info( });
`[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId},
e.loadBot(min);
});
// Serves individual URL for each bot conversational interface...
let url = `/api/messages/${instance.botId}`;
logger.info(
`GeneralBots(${instance.engineName}) listening on: ${url}.`
);
min.dialogs.add('textPrompt', new TextPrompt());
server.post(`/api/messages/${instance.botId}`, (req, res) => {
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);
if (!user.loaded) {
min.conversationalService.sendEvent(
dc,
"loadInstance",
{
instanceId: instance.instanceId,
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey, // TODO: Use token.
}
);
user.loaded = true;
user.subjects = [];
}
logger.info(
`[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId},
ConversationID: ${context.activity.conversation.id}, ConversationID: ${context.activity.conversation.id},
Name: ${context.activity.name}, Text: ${context.activity.text}.` Name: ${context.activity.name}, Text: ${context.activity.text}.`
); );
if (context.activity.type === "conversationUpdate" && if (context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0) { 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 {
logger.info(`Member added to conversation: ${member.name}`);
}
} else if (context.activity.type === 'message') {
// Check to see if anyone replied. If not then start echo dialog
if (context.activity.text === "admin") {
dc.begin("/admin");
} else {
await dc.continue();
}
} else if (context.activity.type === 'event') {
if (context.activity.name === "whoAmI") {
dc.begin("/whoAmI");
} else if (context.activity.name === "showSubjects") {
dc.begin("/menu");
} else if (context.activity.name === "giveFeedback") {
dc.begin("/feedback", {
fromMenu: true
});
} else if (context.activity.name === "showFAQ") {
dc.begin("/faq");
} else if (context.activity.name === "ask") {
dc.begin("/answer", {
// TODO: query: context.activity.data,
fromFaq: true
});
} else if (context.activity.name === "quality") {
dc.begin("/quality", {
// TODO: score: context.activity.data
});
} else {
await dc.continue();
}
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 {
logger.info(`Member added to conversation: ${member.name}`);
}
} else if (context.activity.type === 'message') {
// Check to see if anyone replied. If not then start echo dialog
if (context.activity.text === "admin") {
dc.begin("/admin");
} else {
await dc.continue();
}
} else if (context.activity.type === 'event') {
if (context.activity.name === "whoAmI") {
dc.begin("/whoAmI");
} else if (context.activity.name === "showSubjects") {
dc.begin("/menu");
} else if (context.activity.name === "giveFeedback") {
dc.begin("/feedback", {
fromMenu: true
});
} else if (context.activity.name === "showFAQ") {
dc.begin("/faq");
} else if (context.activity.name === "ask") {
dc.begin("/answer", {
// TODO: query: context.activity.data,
fromFaq: true
});
} else if (context.activity.name === "quality") {
dc.begin("/quality", {
// TODO: score: context.activity.data
});
} else {
await dc.continue();
}
}
}); });
// Serves individual URL for each bot user interface.
let uiUrl = `/${instance.botId}`;
server.use(
uiUrl,
express.static(UrlJoin(this.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();
// Specialized load for each min instance.
cb(min, null);
}); });
// Serves individual URL for each bot user interface.
let uiUrl = `/${instance.botId}`;
server.use(
uiUrl,
express.static(UrlJoin(this.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();
// Specialized load for each min instance.
}); });
} }
/** Performs package deployment in all .gbai or default. */ /** Performs package deployment in all .gbai or default. */
@ -352,7 +335,6 @@ export class GBMinService {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
var _this_ = this;
let totalPackages = 0; let totalPackages = 0;
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
let paths = [this.deployFolder]; let paths = [this.deployFolder];
@ -388,19 +370,19 @@ export class GBMinService {
} }
logger.info(`Starting looking for generalPackages...`); logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`);
paths.forEach(e => { paths.forEach(e => {
logger.info(`Looking in: ${e}...`); logger.info(`Looking in: ${e}...`);
doIt(e); doIt(e);
}); });
/** Deploys all .gbapp files first. */ /** Deploys all .gbapp files first. */
let appPackagesProcessed = 0; let appPackagesProcessed = 0;
gbappPackages.forEach(e => { gbappPackages.forEach(e => {
logger.info(`Deploying app: ${e}...`); logger.info(`Deploying app: ${e}...`);
// Skips .gbapp inside deploy folder. // Skips .gbapp inside deploy folder.
if (!e.startsWith('deploy')) { if (!e.startsWith('deploy')) {
import(e).then(m => { import(e).then(m => {
@ -435,13 +417,11 @@ export class GBMinService {
botPackages.forEach(e => { botPackages.forEach(e => {
logger.info(`Deploying bot: ${e}...`); logger.info(`Deploying bot: ${e}...`);
_this_.deployer.deployBot(e, (data, err) => { this.deployer.deployBot(e, (data, err) => {
logger.info(`Bot: ${e} deployed...`); logger.info(`Bot: ${e} deployed...`);
}); });
}); });
// TODO: Wait here.
/** Then all remaining generalPackages are loaded. */ /** Then all remaining generalPackages are loaded. */
generalPackages.forEach(filename => { generalPackages.forEach(filename => {

View file

@ -41,6 +41,12 @@ import { BotAdapter } from 'botbuilder';
export class FeedbackDialog extends IGBDialog { export class FeedbackDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new CSService(); const service = new CSService();
@ -58,10 +64,9 @@ export class FeedbackDialog extends IGBDialog {
async (dc, value) => { async (dc, value) => {
let rate = value.entity; let rate = value.entity;
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
service.updateConversationRate(user.conversation, rate, item => { await service.updateConversationRate(user.conversation, rate);
let messages = ["Obrigado!", "Obrigado por responder."]; let messages = ["Obrigado!", "Obrigado por responder."];
dc.context.sendActivity(messages[0]); // TODO: Handle rnd. await dc.context.sendActivity(messages[0]); // TODO: Handle rnd.
});
} }
]); ]);

View file

@ -41,6 +41,12 @@ const logger = require("../../../src/logger");
export class QualityDialog extends IGBDialog { export class QualityDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new CSService(); const service = new CSService();
@ -61,25 +67,22 @@ export class QualityDialog extends IGBDialog {
"Lamento... Vamos tentar novamente!", "Lamento... Vamos tentar novamente!",
"Desculpe-me. Por favor, tente escrever de outra forma?" "Desculpe-me. Por favor, tente escrever de outra forma?"
]; ];
dc.context.sendActivity(msg[0]); await dc.context.sendActivity(msg[0]);
} else { } else {
let msg = [ let msg = [
"Ótimo, obrigado por contribuir com sua resposta.", "Ótimo, obrigado por contribuir com sua resposta.",
"Certo, obrigado pela informação.", "Certo, obrigado pela informação.",
"Obrigado pela contribuição." "Obrigado pela contribuição."
]; ];
dc.context.sendActivity(msg[0]); await dc.context.sendActivity(msg[0]);
service.insertQuestionAlternate( await service.insertQuestionAlternate(
min.instance.instanceId, min.instance.instanceId,
user.lastQuestion, user.lastQuestion,
user.lastQuestionId, user.lastQuestionId
(data, err) => {
logger.info("QuestionAlternate inserted.");
}
); );
dc.replace('/ask', {isReturning: true}); await dc.replace('/ask', {isReturning: true});
} }
} }
]); ]);

View file

@ -30,19 +30,6 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const logger = require("../../../src/logger");
const Path = require("path");
const Fs = require("fs");
const FsExtra = require("fs-extra");
const _ = require("lodash");
const Parse = require("csv-parse");
const Async = require("async");
const UrlJoin = require("url-join");
const Walk = require("fs-walk");
const WaitUntil = require("wait-until");
import { GBServiceCallback } from "botlib";
import { GBDeployer } from "../../core.gbapp/services/GBDeployer";
import { GuaribasQuestionAlternate } from '../models'; import { GuaribasQuestionAlternate } from '../models';
import { GuaribasConversation } from '../../analytics.gblib/models'; import { GuaribasConversation } from '../../analytics.gblib/models';
@ -50,44 +37,45 @@ export class CSService {
resolveQuestionAlternate( resolveQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string, questionTyped: string): Promise<GuaribasQuestionAlternate> {
cb: GBServiceCallback<GuaribasQuestionAlternate> return new Promise<GuaribasQuestionAlternate>(
) { (resolve, reject) => {
GuaribasQuestionAlternate.findOne({ GuaribasQuestionAlternate.findOne({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
questionTyped: questionTyped questionTyped: questionTyped
} }
}).then((value: GuaribasQuestionAlternate) => { }).then((value: GuaribasQuestionAlternate) => {
cb(value, null); resolve(value);
}); }).error(reason => reject(reason));
});
} }
insertQuestionAlternate( insertQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string, questionTyped: string,
questionText: string, questionText: string): Promise<GuaribasQuestionAlternate> {
cb: GBServiceCallback<GuaribasQuestionAlternate> return new Promise<GuaribasQuestionAlternate>(
) { (resolve, reject) => {
GuaribasQuestionAlternate.create({ GuaribasQuestionAlternate.create({
questionTyped: questionTyped, questionTyped: questionTyped,
questionText: questionText questionText: questionText
}).then(item => { }).then(item => {
if (cb) { resolve(item);
cb(item, null); }).error(reason => reject(reason));
} });
});
} }
updateConversationRate( updateConversationRate(
conversation: GuaribasConversation, conversation: GuaribasConversation,
rate: number, rate: number
cb: GBServiceCallback<GuaribasConversation> ): Promise<GuaribasConversation> {
) { return new Promise<GuaribasConversation>(
conversation.rate = rate; (resolve, reject) => {
conversation.save().then((value: GuaribasConversation) => { conversation.rate = rate;
cb(conversation, null); conversation.save().then((value: GuaribasConversation) => {
}); resolve(conversation);
}).error(reason => reject(reason));
});
} }
} }

View file

@ -42,6 +42,12 @@ import { LuisRecognizer } from "botbuilder-ai";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
export class AskDialog extends IGBDialog { export class AskDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(); const service = new KBService();

View file

@ -38,31 +38,35 @@ import { BotAdapter } from "botbuilder";
import { GBMinInstance } from "botlib"; import { GBMinInstance } from "botlib";
export class FaqDialog extends IGBDialog { export class FaqDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(); const service = new KBService();
min.dialogs.add("/faq", [ min.dialogs.add("/faq", [
async (dc, args) => { async (dc, args) => {
service.getFaqBySubjectArray("faq", null, (data, err) => { let data = await service.getFaqBySubjectArray("faq", null);
if (data) { if (data) {
min.conversationalService.sendEvent(dc, "play", { await min.conversationalService.sendEvent(dc, "play", {
playerType: "bullet", playerType: "bullet",
data: data.slice(0, 10) data: data.slice(0, 10)
}); });
let messages = [ let messages = [
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", "Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.",
"Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.", "Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.",
"Veja a lista que eu preparei logo aí na tela..." "Veja a lista que eu preparei logo aí na tela..."
]; ];
dc.context.sendActivity(messages[0]); // TODO: RND messages. await dc.context.sendActivity(messages[0]); // TODO: RND messages.
dc.endAll(); await dc.endAll();
} }
});
} }
]); ]);
} }
} }

View file

@ -52,6 +52,12 @@ const WaitUntil = require("wait-until");
export class MenuDialog extends IGBDialog { export class MenuDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
var service = new KBService(); var service = new KBService();
@ -83,16 +89,12 @@ export class MenuDialog extends IGBDialog {
if (user.subjects.length > 0) { if (user.subjects.length > 0) {
service.getFaqBySubjectArray( let data = await service.getFaqBySubjectArray("menu", user.subjects);
"menu", await min.conversationalService.sendEvent(dc, "play", {
user.subjects, playerType: "bullet",
(data, err) => { data: data.slice(0, 6)
min.conversationalService.sendEvent(dc, "play", { });
playerType: "bullet",
data: data.slice(0, 6)
});
}
);
} }
} else { } else {
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
@ -119,64 +121,63 @@ export class MenuDialog extends IGBDialog {
const msg = MessageFactory.text('Greetings from example message'); const msg = MessageFactory.text('Greetings from example message');
var attachments = []; var attachments = [];
service.getSubjectItems( let data = await service.getSubjectItems(
min.instance.instanceId, min.instance.instanceId,
rootSubjectId, rootSubjectId);
data => {
msg.attachmentLayout='carousel';
data.forEach(function (item: GuaribasSubject) {
var subject = item; msg.attachmentLayout = 'carousel';
var card = CardFactory.heroCard(
subject.title,
CardFactory.images([UrlJoin(
"/kb",
min.instance.kb,
"subjects",
subject.internalId + ".png" // TODO: or fallback to subject.png
)]),
CardFactory.actions([
{
type: 'postBack',
title: 'Selecionar',
value: JSON.stringify({
title: subject.title,
subjectId: subject.subjectId,
to: subject.to
})
}]));
attachments.push(card); data.forEach(function (item: GuaribasSubject) {
}); var subject = item;
if (attachments.length == 0) { var card = CardFactory.heroCard(
const user = min.userState.get(dc.context); subject.title,
if (user.subjects && user.subjects.length > 0) { CardFactory.images([UrlJoin(
dc.context.sendActivity( "/kb",
`Vamos pesquisar sobre ${KBService.getFormattedSubjectItems( min.instance.kb,
user.subjects "subjects",
)}?` subject.internalId + ".png" // TODO: or fallback to subject.png
); )]),
} CardFactory.actions([
{
type: 'postBack',
title: 'Selecionar',
value: JSON.stringify({
title: subject.title,
subjectId: subject.subjectId,
to: subject.to
})
}]));
dc.replace("/ask", {}); attachments.push(card);
} else {
msg.attachments = attachments; });
dc.context.sendActivity(msg);
} if (attachments.length == 0) {
const user = min.userState.get(dc.context);
if (user.subjects && user.subjects.length > 0) {
await dc.context.sendActivity(
`Vamos pesquisar sobre ${KBService.getFormattedSubjectItems(
user.subjects
)}?`
);
} }
);
await dc.replace("/ask", {});
} else {
msg.attachments = attachments;
await dc.context.sendActivity(msg);
}
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
user.isAsking = true; user.isAsking = true;
}, },
async (dc, value) => { async (dc, value) => {
var text = value; var text = value;
if (AzureText.isIntentNo(text)) { if (text==="no"||text==="n") { // TODO: Migrate to a common.
dc.replace("/feedback"); dc.replace("/feedback");
} else { } else {
dc.replace("/ask"); dc.replace("/ask");

View file

@ -33,14 +33,17 @@
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
const Path = require("path"); const Path = require("path");
const Fs = require("fs"); const Fs = require("fs");
const Parse = require("csv-parse"); const promise = require('bluebird');
const Async = require("async"); const parse = promise.promisify(require('csv-parse'));
const UrlJoin = require("url-join"); const UrlJoin = require("url-join");
const Walk = require("fs-walk");
const marked = require("marked"); const marked = require("marked");
const path = require("path");
const asyncPromise = require('async-promises');
const walkPromise = require('walk-promise');
import { GBConfigService } from './../../core.gbapp/services/GBConfigService'; import { GBConfigService } from './../../core.gbapp/services/GBConfigService';
import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models"; import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models";
import { GBServiceCallback, IGBCoreService, IGBConversationalService, IGBInstance } from "botlib"; import { IGBCoreService, IGBConversationalService, IGBInstance } from "botlib";
import { AzureSearch } from "pragmatismo-io-framework"; import { AzureSearch } from "pragmatismo-io-framework";
import { GBDeployer } from "../../core.gbapp/services/GBDeployer"; import { GBDeployer } from "../../core.gbapp/services/GBDeployer";
import { GuaribasPackage } from "../../core.gbapp/models/GBModel"; import { GuaribasPackage } from "../../core.gbapp/models/GBModel";
@ -52,55 +55,66 @@ export class KBServiceSearchResults {
export class KBService { export class KBService {
getAnswerById( async getAnswerById(
instanceId: number, instanceId: number,
answerId: number, answerId: number
cb: GBServiceCallback<GuaribasAnswer> ): Promise<GuaribasAnswer> {
) { return new Promise<GuaribasAnswer>(
GuaribasAnswer.findAll({ (resolve, reject) => {
where: {
instanceId: instanceId,
answerId: answerId
}
}).then((item: GuaribasAnswer[]) => {
cb(item[0], null);
});
}
getAnswerByText(
instanceId: number,
text: string,
cb: GBServiceCallback<any>
) {
GuaribasQuestion.findOne({
where: {
instanceId: instanceId,
content: `%${text.trim()}%`
}
}).then((question: GuaribasQuestion) => {
if (question) {
GuaribasAnswer.findAll({ GuaribasAnswer.findAll({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
answerId: question.answerId answerId: answerId
} }
}).then((answer: GuaribasAnswer[]) => { }).then((item: GuaribasAnswer[]) => {
cb({ question: question, answer: answer[0] }, null); resolve(item[0]);
}).error((reason) => {
reject(reason);
}); });
} });
else {
cb(null, null);
}
});
} }
async getAnswerByText(
instanceId: number,
text: string
): Promise<any> {
return new Promise(
(resolve, reject) => {
addAnswer(obj: GuaribasAnswer, cb: GBServiceCallback<GuaribasAnswer>) { GuaribasQuestion.findOne({
GuaribasAnswer.create(obj).then(item => { where: {
if (cb) { instanceId: instanceId,
cb(item, null); content: `%${text.trim()}%`
} }
}); }).then((question: GuaribasQuestion) => {
if (question) {
GuaribasAnswer.findAll({
where: {
instanceId: instanceId,
answerId: question.answerId
}
}).then((answer: GuaribasAnswer[]) => {
resolve({ question: question, answer: answer[0] });
});
}
else {
resolve(null);
}
}).error((reason) => {
reject(reason);
});
});
}
async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> {
return new Promise<GuaribasAnswer>(
(resolve, reject) => {
GuaribasAnswer.create(obj).then(item => {
resolve(item);
}).error((reason) => {
reject(reason);
});
});
} }
async ask( async ask(
@ -110,32 +124,25 @@ export class KBService {
subjects: GuaribasSubject[] subjects: GuaribasSubject[]
): Promise<KBServiceSearchResults> { ): Promise<KBServiceSearchResults> {
return new Promise<KBServiceSearchResults>((resolve, reject) => { // Builds search query.
// Builds search query. what = what.toLowerCase();
what = what.replace("?", " ");
what = what.replace("!", " ");
what = what.replace(".", " ");
what = what.replace("/", " ");
what = what.replace("\\", " ");
what = what.toLowerCase(); if (subjects) {
what = what.replace("?", " "); let text = KBService.getSubjectItemsSeparatedBySpaces(
what = what.replace("!", " "); subjects
what = what.replace(".", " "); );
what = what.replace("/", " "); if (text) {
what = what.replace("\\", " "); what = `${what} ${text}`;
if (subjects) {
let text = KBService.getSubjectItemsSeparatedBySpaces(
subjects
);
if (text) {
what = `${what} ${text}`;
}
} }
}
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`; // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`;
try {
// Performs search.
var _this_ = this;
if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") { if (instance.searchKey && GBConfigService.get("DATABASE_DIALECT") == "mssql") {
let service = new AzureSearch( let service = new AzureSearch(
instance.searchKey, instance.searchKey,
@ -143,41 +150,24 @@ export class KBService {
instance.searchIndex, instance.searchIndex,
instance.searchIndexer instance.searchIndexer
); );
let results = await service.search(what);
service.search(what, (err: any, results: any) => { if (results && results.length > 0 &&
if (results && results.length > 0) { results[0]["@search.score"] >= searchScore) {
// Ponders over configuration. let value = await this.getAnswerById(
instance.instanceId,
if (results[0]["@search.score"] >= searchScore) { results[0].answerId);
_this_.getAnswerById( return Promise.resolve({ answer: value, questionId: results[0].questionId });
instance.instanceId, }
results[0].answerId,
(answer, err) => {
if (err) { reject(err); } else {
resolve({ answer: answer, questionId: results[0].questionId });
}
}
);
} else {
resolve(null);
}
} else {
resolve(null);
}
});
} else { } else {
this.getAnswerByText(instance.instanceId, what, (data, err) => { let data = await this.getAnswerByText(instance.instanceId, what);
if (data) { return Promise.resolve(
resolve({ answer: data.answer, questionId: data.question.questionId }); { answer: data.answer, questionId: data.question.questionId }
} );
else {
if (err) { reject(err); } else {
resolve(null);
}
}
});
} }
}); }
catch (reason) {
return Promise.reject(reason);
}
} }
getSearchSchema(indexName) { getSearchSchema(indexName) {
@ -298,166 +288,179 @@ export class KBService {
return out.join(" "); return out.join(" ");
} }
getSubjectItems( async getSubjectItems(
instanceId: number, instanceId: number,
parentId: number, parentId: number
cb: GBServiceCallback<GuaribasSubject[]> ): Promise<GuaribasSubject[]> {
) { return new Promise<GuaribasSubject[]>(
var where = { parentSubjectId: parentId, instanceId: instanceId }; (resolve, reject) => {
GuaribasSubject.findAll({ var where = { parentSubjectId: parentId, instanceId: instanceId };
where: where GuaribasSubject.findAll({
}) where: where
.then((values: GuaribasSubject[]) => { })
cb(values, null); .then((values: GuaribasSubject[]) => {
}) resolve(values);
.error(reason => { })
cb(null, reason); .error(reason => {
reject(reason);
});
}); });
} }
getFaqBySubjectArray(from: string, subjects: any, cb) { async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> {
let where = { return new Promise<GuaribasQuestion[]>(
from: from (resolve, reject) => {
};
if (subjects) { let where = {
if (subjects[0]) { from: from
where["subject1"] = subjects[0].title; };
}
if (subjects[1]) { if (subjects) {
where["subject2"] = subjects[1].title; if (subjects[0]) {
} where["subject1"] = subjects[0].title;
if (subjects[2]) {
where["subject3"] = subjects[2].title;
}
if (subjects[3]) {
where["subject4"] = subjects[3].title;
}
}
GuaribasQuestion.findAll({
where: where
})
.then((items: GuaribasQuestion[]) => {
if (!items) items = [];
if (items.length == 0) {
cb([], null);
} else {
cb(items, null);
}
})
.catch(reason => {
if (reason.message.indexOf("no such table: IGBInstance") != -1) {
cb([], null);
} else {
cb(null, reason);
logger.info(`GuaribasServiceError: ${reason}`);
}
});
}
importKbTabularFile(
basedir: string,
filename: string,
instanceId: number,
packageId: number,
cb
) {
var filePath = UrlJoin(basedir, filename);
var parser = Parse(
{
delimiter: "\t"
},
function (err, data) {
Async.eachSeries(data, function (line, callback) {
callback();
let subjectsText = line[0];
var from = line[1];
var to = line[2];
var question = line[3];
var answer = line[4];
// Skip the first line.
if (!(subjectsText === "subjects" && from == "from")) {
let format = ".txt";
// Extract answer from external media if any.
if (answer.indexOf(".md") > -1) {
let mediaFilename = UrlJoin(basedir, "..", "articles", answer);
if (Fs.existsSync(mediaFilename)) {
answer = Fs.readFileSync(mediaFilename, "utf8");
format = ".md";
} else {
logger.info("[GBImporter] File not found: ", mediaFilename);
answer =
"Por favor, contate a administração para rever esta pergunta.";
}
}
let subjectArray = subjectsText.split(".");
let subject1: string,
subject2: string,
subject3: string,
subject4: string;
var indexer = 0;
subjectArray.forEach(element => {
if (indexer == 0) {
subject1 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 1) {
subject2 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 2) {
subject3 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 3) {
subject4 = subjectArray[indexer].substring(0, 63);
}
indexer++;
});
GuaribasAnswer.create({
instanceId: instanceId,
content: answer,
format: format,
packageId: packageId
}).then(function (answer: GuaribasAnswer) {
GuaribasQuestion.create({
from: from,
to: to,
subject1: subject1,
subject2: subject2,
subject3: subject3,
subject4: subject4,
content: question,
instanceId: instanceId,
answerId: answer.answerId,
packageId: packageId
});
});
} else {
logger.warn("[GBImporter] Missing header in file: ", filename);
} }
});
} if (subjects[1]) {
); where["subject2"] = subjects[1].title;
Fs.createReadStream(filePath, { }
encoding: "UCS-2"
}).pipe(parser); if (subjects[2]) {
where["subject3"] = subjects[2].title;
}
if (subjects[3]) {
where["subject4"] = subjects[3].title;
}
}
GuaribasQuestion.findAll({
where: where
})
.then((items: GuaribasQuestion[]) => {
if (!items) items = [];
if (items.length == 0) {
resolve([]);
} else {
resolve(items);
}
})
.catch(reason => {
if (reason.message.indexOf("no such table: IGBInstance") != -1) {
resolve([]);
} else {
reject(reason);
logger.info(`GuaribasServiceError: ${reason}`);
}
});
});
} }
sendAnswer(conversationalService: IGBConversationalService, dc: any, answer: GuaribasAnswer) { async importKbTabularFile(
filePath: string,
instanceId: number,
packageId: number
): Promise<GuaribasQuestion[]> {
return new Promise<GuaribasQuestion[]>(
(resolve, reject) => {
let file = Fs.readFileSync(filePath, "UCS-2");
let opts = {
delimiter: "\t"
};
var parser = parse(file, opts).then((data) => {
asyncPromise.eachSeries(data, (line) => {
return new Promise((resolve, reject) => {
// Extracts values from columns in the current line.
let subjectsText = line[0];
var from = line[1];
var to = line[2];
var question = line[3];
var answer = line[4];
// Skips the first line.
if (!(subjectsText === "subjects" && from == "from")) {
let format = ".txt";
// Extracts answer from external media if any.
if (answer.indexOf(".md") > -1) {
let mediaFilename = UrlJoin(path.dirname(filePath), "..", "articles", answer);
if (Fs.existsSync(mediaFilename)) {
answer = Fs.readFileSync(mediaFilename, "utf8");
format = ".md";
} else {
logger.info("[GBImporter] File not found: ", mediaFilename);
answer =
"Por favor, contate a administração para rever esta pergunta.";
}
}
// Processes subjects hierarchy splitting by dots.
let subjectArray = subjectsText.split(".");
let subject1: string, subject2: string, subject3: string,
subject4: string;
var indexer = 0;
subjectArray.forEach(element => {
if (indexer == 0) {
subject1 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 1) {
subject2 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 2) {
subject3 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 3) {
subject4 = subjectArray[indexer].substring(0, 63);
}
indexer++;
});
// Now with all the data ready, creates entities in the store.
GuaribasAnswer.create({
instanceId: instanceId,
content: answer,
format: format,
packageId: packageId
}).then((answer: GuaribasAnswer) => {
GuaribasQuestion.create({
from: from,
to: to,
subject1: subject1,
subject2: subject2,
subject3: subject3,
subject4: subject4,
content: question,
instanceId: instanceId,
answerId: answer.answerId,
packageId: packageId
}).then((question: GuaribasQuestion) => {
resolve(question);
}).error(reason => reject(reason));
}).error(reason => reject(reason));;
} else {
logger.warn("[GBImporter] Missing header in file: ", filePath);
}
});
});
}).error(reason => reject(reason));
});
}
sendAnswer(conversationalService: IGBConversationalService,
dc: any, answer: GuaribasAnswer) {
if (answer.content.endsWith('.mp4')) { if (answer.content.endsWith('.mp4')) {
conversationalService.sendEvent(dc, "play", { conversationalService.sendEvent(dc, "play", {
playerType: "video", playerType: "video",
data: answer.content data: answer.content
}); });
} else if (answer.content.length > 140 && dc.message.source != "directline") { } else if (answer.content.length > 140 &&
dc.message.source != "directline") {
let messages = [ let messages = [
"Vou te responder na tela para melhor visualização...", "Vou te responder na tela para melhor visualização...",
"A resposta está na tela...", "A resposta está na tela...",
@ -486,120 +489,124 @@ export class KBService {
} }
} }
async importKbPackage(
importKbPackage(
localPath: string, localPath: string,
packageStorage: GuaribasPackage, packageStorage: GuaribasPackage,
instance: IGBInstance instance: IGBInstance
) { ): Promise<GuaribasQuestion[]> {
this.importSubjectFile( return new Promise<GuaribasQuestion[]>(
packageStorage.packageId, (resolve, reject) => {
UrlJoin(localPath, "subjects.json"),
instance // Imports subjects tree into database and return it.
);
let _this_ = this; this.importSubjectFile(
setTimeout(() => { packageStorage.packageId,
_this_.importKbTabularDirectory( UrlJoin(localPath, "subjects.json"),
localPath, instance
instance, ).then((value: GuaribasQuestion[]) => {
packageStorage.packageId
); // Import all .tsv files in the tabular directory.
}, 3000);
this.importKbTabularDirectory(
localPath,
instance,
packageStorage.packageId
);
});
});
} }
importKbTabularDirectory( importKbTabularDirectory(
localPath: string, localPath: string,
instance: IGBInstance, instance: IGBInstance,
packageId: number packageId: number
) { ): Promise<GuaribasQuestion[]> {
let _this_ = this; return new Promise(
Walk.files( (resolve, reject) => {
UrlJoin(localPath, "tabular"),
(basedir, filename, stat, next) => { walkPromise(UrlJoin(localPath, "tabular")).then((files) => {
if (filename.endsWith(".tsv")) { files.array.forEach(file => {
_this_.importKbTabularFile( if (file.endsWith(".tsv")) {
basedir, this.importKbTabularFile(
filename, file,
instance.instanceId, instance.instanceId,
packageId, packageId);
(data, err) => {
if (err) {
logger.info(err);
} else {
logger.info("Import KB done.");
}
} }
);
}
},
function (err) {
if (err) logger.info(err);
}
);
}
importSubjectFile(
packageId: number,
filename: string,
instance: IGBInstance
) {
var subjects = JSON.parse(Fs.readFileSync(filename, "utf8"));
function doIt(subjects: GuaribasSubject[], parentSubjectId: number) {
Async.eachSeries(subjects, (item, callback) => {
let mediaFilename = item.id + ".png";
GuaribasSubject.create({
internalId: item.id,
parentSubjectId: parentSubjectId,
instanceId: instance.instanceId,
from: item.from,
to: item.to,
title: item.title,
description: item.description,
packageId: packageId
}).then((value: any) => {
if (item.children) {
doIt(item.children, value.subjectId);
}
});
callback();
});
}
doIt(subjects.children, null);
}
undeployKbFromStorage(
instance: IGBInstance,
packageId: number,
cb: GBServiceCallback<any>
) {
GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasPackage.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
cb(null, null);
}); });
}); });
}); });
}); }
async importSubjectFile(
packageId: number,
filename: string,
instance: IGBInstance
): Promise<GuaribasQuestion[]> {
return new Promise<GuaribasQuestion[]>(
(resolve, reject) => {
var subjects = JSON.parse(Fs.readFileSync(filename, "utf8"));
const doIt = (subjects: GuaribasSubject[], parentSubjectId: number) =>
new Promise((resolve, reject) => {
asyncPromise.eachSeries(subjects, (item, callback) => {
let mediaFilename = item.id + ".png";
GuaribasSubject.create({
internalId: item.id,
parentSubjectId: parentSubjectId,
instanceId: instance.instanceId,
from: item.from,
to: item.to,
title: item.title,
description: item.description,
packageId: packageId
}).then((value: any) => {
if (item.children) {
doIt(item.children, value.subjectId);
}
});
callback();
});
});
doIt(subjects.children, null);
resolve()
});
}
undeployKbFromStorage(
instance: IGBInstance,
packageId: number
) {
// TODO: call reject.
return new Promise(
(resolve, reject) => {
GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
GuaribasPackage.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
resolve(null);
});
});
});
});
});
} }
/** /**
* Deploys a knowledge base to the storage using the .gbkb format. * Deploys a knowledge base to the storage using the .gbkb format.
* *
* @param localPath Path to the .gbkb folder. * @param localPath Path to the .gbkb folder.
* @param cb Package instance or error info. */
*/ async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) {
deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, cb: GBServiceCallback<any>) {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); let packageName = Path.basename(localPath);
logger.info("[GBDeployer] Opening package: ", packageName); logger.info("[GBDeployer] Opening package: ", packageName);
@ -607,18 +614,10 @@ export class KBService {
Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8")
); );
core.loadInstance(packageObject.botId, (instance, err) => { let instance = await core.loadInstance(packageObject.botId);
deployer.deployPackageToStorage( let p = await deployer.deployPackageToStorage(
instance.instanceId, instance.instanceId,
packageName, packageName);
(p, err) => { await this.importKbPackage(localPath, p, instance);
this.importKbPackage(localPath, p, instance);
setTimeout(() => {
cb(null, null);
}, 8000);
}
);
});
} }
} }

View file

@ -39,13 +39,13 @@ const UrlJoin = require("url-join");
const Walk = require("fs-walk"); const Walk = require("fs-walk");
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { GBServiceCallback, GBService, IGBInstance } from "botlib"; import { GBServiceCallback, GBService, IGBInstance } from "botlib";
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from "../models"; import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from "../models";
export class SecService extends GBService { export class SecService extends GBService {
importSecurityFile(localPath: string, instance: IGBInstance) { async importSecurityFile(localPath: string, instance: IGBInstance) {
var security = JSON.parse( let security = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "security.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, "security.json"), "utf8")
); );
security.groups.forEach(group => { security.groups.forEach(group => {
@ -61,43 +61,46 @@ export class SecService extends GBService {
userName: user.userName userName: user.userName
}); });
userDb.save().then(userDb => { userDb.save().then(userDb => {
let userGroup = GuaribasUserGroup.build(); let userGroup = GuaribasUserGroup.build();
userGroup.groupId = groupDb.groupId; userGroup.groupId = groupDb.groupId;
userGroup.userId = userDb.userId; userGroup.userId = userDb.userId;
userGroup.save(); userGroup.save();
}); });
}); });
}); });
}); });
} }
ensureUser( async ensureUser(
instanceId: number, instanceId: number,
userSystemId: string, userSystemId: string,
userName: string, userName: string,
address: string, address: string,
channelName: string, channelName: string,
displayName: string, displayName: string
cb: GBServiceCallback<GuaribasUser> ): Promise<GuaribasUser> {
) { return new Promise<GuaribasUser>(
GuaribasUser.findOne({ (resolve, reject) => {
attributes: ["instanceId", "internalAddress"],
where: { GuaribasUser.findOne({
instanceId: instanceId, attributes: ["instanceId", "internalAddress"],
userSystemId: userSystemId where: {
} instanceId: instanceId,
}).then(user => { userSystemId: userSystemId
if (!user) { }
user = GuaribasUser.build(); }).then(user => {
} if (!user) {
user.userSystemId = userSystemId; user = GuaribasUser.build();
user.userName = userName; }
user.displayName = displayName; user.userSystemId = userSystemId;
user.internalAddress = address; user.userName = userName;
user.email = userName; user.displayName = displayName;
user.defaultChannel = channelName; user.internalAddress = address;
user.save(); user.email = userName;
cb(user, null); user.defaultChannel = channelName;
}); user.save();
resolve(user);
}).error(reason => reject(reason));
});
} }
} }

View file

@ -138,7 +138,6 @@ export class WhatsappDirectLine extends GBService {
.then((conversationId) => { .then((conversationId) => {
this.conversationIds[from] = conversationId; this.conversationIds[from] = conversationId;
this.inputMessage(client, conversationId, text, this.inputMessage(client, conversationId, text,
from, fromName); from, fromName);

View file

@ -56,6 +56,7 @@
"tedious": "^2.6.4", "tedious": "^2.6.4",
"url-join": "^4.0.0", "url-join": "^4.0.0",
"wait-until": "^0.0.2", "wait-until": "^0.0.2",
"walk-promise": "^0.2.0",
"winston": "^3.0.0" "winston": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -77,16 +77,18 @@ export class GBServer {
extended: true extended: true
})); }));
server.listen(port, () => { server.listen(port, async () => {
logger.info(`Accepting connections on ${port}...`); try {
logger.info(`Starting instances...`);
// Reads basic configuration, initialize minimal services. logger.info(`Accepting connections on ${port}...`);
logger.info(`Starting instances...`);
GBConfigService.init(); // Reads basic configuration, initialize minimal services.
let core = new GBCoreService();
core.initDatabase(() => { GBConfigService.init();
let core = new GBCoreService();
await core.initDatabase();
// Boot a bot package if any. // Boot a bot package if any.
@ -104,23 +106,16 @@ export class GBServer {
p.loadPackage(core, core.sequelize); p.loadPackage(core, core.sequelize);
}); });
(async () => { await minService.deployPackages(core, server, appPackages);
try { logger.info(`The Bot Server is in RUNNING mode...`);
await minService.deployPackages(core, server, appPackages);
logger.info(`The Bot Server is in RUNNING mode...`);
minService.buildMin(instance => { let instance = await minService.buildMin(server, appPackages);
logger.info(`Instance loaded: ${instance.botId}...`); logger.info(`Instance loaded: ${instance.botId}...`);
}, server, appPackages); return core;
} catch (err) {
logger.info(err);
}
} catch (err) {
logger.info(err);
}
})()
});
return core;
}); });
} }
} }