Adding tslint.json and working on Cognitive Services NPM package upgrade.

This commit is contained in:
Rodrigo Rodriguez 2018-09-11 19:33:58 -03:00
parent bd03cfbc6c
commit d07b6350a0
11 changed files with 658 additions and 606 deletions

View file

@ -52,7 +52,6 @@ import {
CreatedAt, CreatedAt,
UpdatedAt, UpdatedAt,
DataType, DataType,
IsUUID,
PrimaryKey, PrimaryKey,
AutoIncrement AutoIncrement
} from "sequelize-typescript"; } from "sequelize-typescript";
@ -90,6 +89,8 @@ export class GuaribasInstance extends Model<GuaribasInstance> implements IGBInst
@Column textAnalyticsKey: string; @Column textAnalyticsKey: string;
@Column textAnalyticsServerUrl: string;
@Column marketplacePassword: string; @Column marketplacePassword: string;
@Column webchatKey: string; @Column webchatKey: string;

View file

@ -30,78 +30,77 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { GBCoreService } from "./GBCoreService"; import { GBCoreService } from "./GBCoreService";
import { IGBConversationalService } from "botlib"; import { IGBConversationalService } from "botlib";
import { GBError } 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 interface LanguagePickerSettings {
defaultLocale?: string;
supportedLocales?: string[];
}
export class GBConversationalService implements IGBConversationalService { export class GBConversationalService implements IGBConversationalService {
coreService: GBCoreService;
coreService: GBCoreService; constructor(coreService: GBCoreService) {
this.coreService = coreService;
}
constructor(coreService: GBCoreService) { getCurrentLanguage(dc: any) {
this.coreService = coreService; return dc.context.activity.locale;
} }
async sendEvent(dc: any, name: string, value: any): Promise<any> { async sendEvent(dc: any, name: string, value: any): Promise<any> {
const msg = MessageFactory.text(''); const msg = MessageFactory.text("");
msg.value = value; msg.value = value;
msg.type = "event"; msg.type = "event";
msg.name = name; msg.name = name;
return dc.context.sendActivity(msg); return dc.context.sendActivity(msg);
} }
async runNLP( async runNLP(dc: any, min: GBMinInstance, text: string): Promise<any> {
dc: any, // Invokes LUIS.
min: GBMinInstance,
text: string const model = new LuisRecognizer({
): Promise<any> { appId: min.instance.nlpAppId,
subscriptionKey: min.instance.nlpSubscriptionKey,
const model = new LuisRecognizer({ serviceEndpoint: min.instance.nlpServerUrl
appId: min.instance.nlpAppId, });
subscriptionKey: min.instance.nlpSubscriptionKey, let res = await model.recognize(dc.context);
serviceEndpoint: min.instance.nlpServerUrl
}); // Resolves intents returned from LUIS.
let res = await model.recognize(dc.context); let topIntent = LuisRecognizer.topIntent(res);
if (topIntent) {
// Resolve intents returned from LUIS var intent = topIntent;
let topIntent = LuisRecognizer.topIntent(res); var entity =
res.entities && res.entities.length > 0
if (topIntent) { ? res.entities[0].entity.toUpperCase()
var intent = topIntent; : null;
var entity = logger.info("luis: intent: [" + intent + "] entity: [" + entity + "]");
res.entities && res.entities.length > 0
? res.entities[0].entity.toUpperCase() try {
: null; await dc.replace("/" + intent);
logger.info( } catch (error) {
"luis: intent: [" + intent + "] entity: [" + entity + "]" logger.info("error: intent: [" + intent + "] error: [" + error + "]");
); await dc.context.sendActivity(
"Desculpe-me, não encontrei nada a respeito..."
try { );
await dc.replace("/" + intent); await dc.replace("/ask", { isReturning: true });
} catch (error) { }
logger.info("error: intent: [" + intent + "] error: [" + error + "]");
await dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito..."); return Promise.resolve({ intent, entities: res.entities });
await dc.replace("/ask", { isReturning: true }); } else {
} await dc.context.sendActivity("Lamento, não achei nada a respeito...");
await dc.replace("/ask", { isReturning: true });
return Promise.resolve({ intent, entities: res.entities });
return Promise.resolve(null);
} else {
await dc.context.sendActivity("Lamento, não achei nada a respeito...");
await dc.replace("/ask", { isReturning: true });
return Promise.resolve(null);
}
} }
}
} }

View file

@ -33,16 +33,15 @@
"use strict"; "use strict";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { Sequelize } from 'sequelize-typescript'; import { Sequelize } from "sequelize-typescript";
import { GBConfigService } from "./GBConfigService"; import { GBConfigService } from "./GBConfigService";
import { IGBInstance, IGBCoreService } from 'botlib'; import { IGBInstance, IGBCoreService } from "botlib";
import { GuaribasInstance } from "../models/GBModel"; import { GuaribasInstance } from "../models/GBModel";
/** /**
* Core service layer. * Core service layer.
*/ */
export class GBCoreService implements IGBCoreService { export class GBCoreService implements IGBCoreService {
/** /**
* Data access layer instance. * Data access layer instance.
*/ */
@ -63,8 +62,8 @@ export class GBCoreService implements IGBCoreService {
*/ */
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;
@ -75,212 +74,245 @@ export class GBCoreService implements IGBCoreService {
this.dialect = GBConfigService.get("DATABASE_DIALECT"); this.dialect = GBConfigService.get("DATABASE_DIALECT");
} }
/** /**
* Gets database config and connect to storage. * Gets database config and connect to storage.
*/ */
async initDatabase() { async initDatabase() {
return new Promise( return new Promise((resolve, reject) => {
(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 logging =
let database: string | undefined; GBConfigService.get("DATABASE_LOGGING") === "true"
let username: string | undefined; ? (str: string) => {
let password: string | undefined; logger.info(str);
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); }
: false; : false;
let encrypt = (GBConfigService.get("DATABASE_ENCRYPT") === "true"); let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true";
this.sequelize = new Sequelize({ this.sequelize = new Sequelize({
host: host, host: host,
database: database, database: database,
username: username, username: username,
password: password, password: password,
logging: logging, logging: logging,
operatorsAliases: false, operatorsAliases: false,
dialect: this.dialect, dialect: this.dialect,
storage: storage, storage: storage,
dialectOptions: { dialectOptions: {
encrypt: encrypt encrypt: encrypt
}, },
pool: { pool: {
max: 32, max: 32,
min: 8, min: 8,
idle: 40000, idle: 40000,
evict: 40000, evict: 40000,
acquire: 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);
} }
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 { private createTableQueryOverride(tableName, attributes, options): string {
let sql: string = this.createTableQuery.apply(this.queryGenerator, let sql: string = this.createTableQuery.apply(this.queryGenerator, [
[tableName, attributes, options]); tableName,
// let sql: string = '' + attributes,
// 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + options
// '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);';
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
if (matches) { if (matches) {
const table = matches[1]; const table = matches[1];
const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/; const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/;
sql = sql.replace(re2, (match: string, ...args: any[]): string => { sql = sql.replace(
return 'CONSTRAINT [' + table + '_pk] ' + match; re2,
}); (match: string, ...args: any[]): string => {
return "CONSTRAINT [" + table + "_pk] " + match;
}
);
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re4 = /\[([^\]]*)\]/g; const re4 = /\[([^\]]*)\]/g;
sql = sql.replace(re3, (match: string, ...args: any[]): string => { sql = sql.replace(
const fkcols = args[0]; re3,
let fkname = table; (match: string, ...args: any[]): string => {
let matches = re4.exec(fkcols); const fkcols = args[0];
while (matches != null) { let fkname = table;
fkname += '_' + matches[1]; let matches = re4.exec(fkcols);
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; 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 { private changeColumnQueryOverride(tableName, attributes): string {
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [
[tableName, attributes]); tableName,
// let sql = '' + attributes
// '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';
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
if (matches) { if (matches) {
const table = matches[1]; const table = matches[1];
const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re3 = /\[([^\]]*)\]/g; const re3 = /\[([^\]]*)\]/g;
sql = sql.replace(re2, (match: string, ...args: any[]): string => { sql = sql.replace(
const fkcols = args[2]; re2,
let fkname = table; (match: string, ...args: any[]): string => {
let matches = re3.exec(fkcols); const fkcols = args[2];
while (matches != null) { let fkname = table;
fkname += '_' + matches[1]; let matches = re3.exec(fkcols);
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; return sql;
} }
async syncDatabaseStructure() { async syncDatabaseStructure() {
return new Promise( return new Promise((resolve, reject) => {
(resolve, reject) => { if (GBConfigService.get("DATABASE_SYNC") === "true") {
if (GBConfigService.get("DATABASE_SYNC") === "true") { const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true";
const alter = (GBConfigService.get("DATABASE_SYNC_ALTER") === "true"); const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true";
const force = (GBConfigService.get("DATABASE_SYNC_FORCE") === "true"); logger.info("Syncing database...");
logger.info("Syncing database..."); this.sequelize
this.sequelize.sync({ .sync({
alter: alter, alter: alter,
force: force force: force
}).then(value => { })
logger.info("Database synced."); .then(
resolve(value); value => {
}, err => reject(err)); logger.info("Database synced.");
} else { resolve(value);
logger.info("Database synchronization is disabled."); },
resolve(); err => reject(err)
} );
}); } else {
logger.info("Database synchronization is disabled.");
resolve();
}
});
} }
/** /**
* Loads all items to start several listeners. * Loads all items to start several listeners.
*/ */
async loadInstances(): Promise<IGBInstance> { async loadInstances(): Promise<IGBInstance> {
return new Promise( return new Promise((resolve, reject) => {
(resolve, reject) => { GuaribasInstance.findAll({})
GuaribasInstance.findAll({}) .then((items: IGBInstance[]) => {
.then((items: IGBInstance[]) => { if (!items) items = [];
if (!items) items = [];
if (items.length == 0) { if (items.length == 0) {
resolve([]); resolve([]);
} else { } else {
resolve(items); resolve(items);
} }
}) })
.catch(reason => { .catch(reason => {
if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { if (reason.message.indexOf("no such table: GuaribasInstance") != -1) {
resolve([]); resolve([]);
} else { } else {
logger.info(`GuaribasServiceError: ${reason}`); logger.info(`GuaribasServiceError: ${reason}`);
reject(reason); reject(reason);
} }
}); });
}); });
} }
/** /**
* Loads just one Bot instance. * Loads just one Bot instance.
*/ */
async loadInstance(botId: string): Promise<IGBInstance> { async loadInstance(botId: string): Promise<IGBInstance> {
return new Promise<IGBInstance>( return new Promise<IGBInstance>((resolve, reject) => {
(resolve, reject) => { let options = { where: {} };
let options = { where: {} }; if (botId != "[default]") {
options.where = { botId: botId };
}
if (botId != "[default]") { GuaribasInstance.findOne(options)
options.where = { botId: botId }; .then((instance: IGBInstance) => {
} if (instance) {
resolve(instance);
GuaribasInstance.findOne(options) } else {
.then((instance: IGBInstance) => { resolve(null);
if (instance) { }
resolve(instance); })
} else { .catch(err => {
resolve(null); logger.info(`GuaribasServiceError: ${err}`);
} reject(err);
}) });
.catch(err => { });
logger.info(`GuaribasServiceError: ${err}`);
reject(err);
});
});
} }
} }

View file

@ -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 WaitUntil = require("wait-until");
const express = require("express"); const express = require("express");
import { KBService } from './../../kb.gbapp/services/KBService'; import { KBService } from "./../../kb.gbapp/services/KBService";
import { GBImporter } from "./GBImporter"; import { GBImporter } from "./GBImporter";
import { IGBCoreService, IGBInstance } from "botlib"; import { IGBCoreService, IGBInstance } from "botlib";
import { GBConfigService } from "./GBConfigService"; import { GBConfigService } from "./GBConfigService";
import { GBError } from "botlib"; import { GBError } from "botlib";
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. */
export class GBDeployer { export class GBDeployer {
@ -62,13 +62,16 @@ export class GBDeployer {
this.importer = importer; 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<IGBPackage>) { public deployPackages(
core: IGBCoreService,
server: any,
appPackages: Array<IGBPackage>
) {
let _this = this; let _this = this;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
@ -83,31 +86,31 @@ export class GBDeployer {
let generalPackages = new Array<string>(); let generalPackages = new Array<string>();
function doIt(path) { function doIt(path) {
const isDirectory = source => Fs.lstatSync(source).isDirectory() const isDirectory = source => Fs.lstatSync(source).isDirectory();
const getDirectories = source => 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); let dirs = getDirectories(path);
dirs.forEach(element => { dirs.forEach(element => {
if (element.startsWith('.')) { if (element.startsWith(".")) {
logger.info(`Ignoring ${element}...`); logger.info(`Ignoring ${element}...`);
} } else {
else { if (element.endsWith(".gbot")) {
if (element.endsWith('.gbot')) {
botPackages.push(element); botPackages.push(element);
} } else if (element.endsWith(".gbapp")) {
else if (element.endsWith('.gbapp')) {
gbappPackages.push(element); gbappPackages.push(element);
} } else {
else {
generalPackages.push(element); 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 => { paths.forEach(e => {
logger.info(`Looking in: ${e}...`); logger.info(`Looking in: ${e}...`);
doIt(e); doIt(e);
@ -121,31 +124,32 @@ export class GBDeployer {
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)
let p = new m.Package(); .then(m => {
p.loadPackage(core, core.sequelize); let p = new m.Package();
appPackages.push(p); p.loadPackage(core, core.sequelize);
logger.info(`App (.gbapp) deployed: ${e}.`); appPackages.push(p);
appPackagesProcessed++; logger.info(`App (.gbapp) deployed: ${e}.`);
}).catch(err => { appPackagesProcessed++;
logger.info(`Error deploying App (.gbapp): ${e}: ${err}`); })
appPackagesProcessed++; .catch(err => {
}); logger.info(`Error deploying App (.gbapp): ${e}: ${err}`);
appPackagesProcessed++;
});
} else { } else {
appPackagesProcessed++; appPackagesProcessed++;
} }
}); });
WaitUntil() WaitUntil()
.interval(1000) .interval(1000)
.times(10) .times(10)
.condition(function (cb) { .condition(function(cb) {
logger.info(`Waiting for app package deployment...`); logger.info(`Waiting for app package deployment...`);
cb(appPackagesProcessed == gbappPackages.length); cb(appPackagesProcessed == gbappPackages.length);
}) })
.done(function (result) { .done(function(result) {
logger.info(`App Package deployment done.`); logger.info(`App Package deployment done.`);
core.syncDatabaseStructure(); core.syncDatabaseStructure();
@ -161,39 +165,39 @@ export class GBDeployer {
/** Then all remaining generalPackages are loaded. */ /** Then all remaining generalPackages are loaded. */
generalPackages.forEach(filename => { generalPackages.forEach(filename => {
let filenameOnly = Path.basename(filename); let filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`); logger.info(`Deploying package: ${filename}...`);
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */ /** 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. */ /** Themes for bots. */
} else if (Path.extname(filename) === ".gbtheme") { } else if (Path.extname(filename) === ".gbtheme") {
server.use("/themes/" + filenameOnly, express.static(filename)); 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. */ /** Knowledge base for bots. */
} else if (Path.extname(filename) === ".gbkb") { } else if (Path.extname(filename) === ".gbkb") {
server.use( server.use(
"/kb/" + filenameOnly + "/subjects", "/kb/" + filenameOnly + "/subjects",
express.static(UrlJoin(filename, "subjects")) express.static(UrlJoin(filename, "subjects"))
); );
logger.info(`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`); logger.info(
} `KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`
);
else if (Path.extname(filename) === ".gbui" || filename.endsWith(".git")) { } else if (
Path.extname(filename) === ".gbui" ||
filename.endsWith(".git")
) {
// Already Handled // Already Handled
} } else {
/** Unknown package format. */
/** Unknown package format. */
else {
let err = new Error(`Package type not handled: ${filename}.`); let err = new Error(`Package type not handled: ${filename}.`);
reject(err); reject(err);
} }
@ -203,30 +207,30 @@ export class GBDeployer {
WaitUntil() WaitUntil()
.interval(1000) .interval(1000)
.times(5) .times(5)
.condition(function (cb) { .condition(function(cb) {
logger.info(`Waiting for package deployment...`); logger.info(`Waiting for package deployment...`);
cb(totalPackages == (generalPackages.length)); cb(totalPackages == generalPackages.length);
}) })
.done(function (result) { .done(function(result) {
if (botPackages.length === 0) { 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.`); logger.info(
} "The server is running with no bot instances, at least one .gbot file must be deployed."
else { );
} else {
logger.info(`Package deployment done.`); logger.info(`Package deployment done.`);
} }
resolve(); resolve();
}); });
}); });
} catch (err) { } catch (err) {
logger.error(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<IGBInstance> { async deployBot(localPath: string): Promise<IGBInstance> {
@ -241,19 +245,18 @@ export class GBDeployer {
async deployPackageToStorage( async deployPackageToStorage(
instanceId: number, instanceId: number,
packageName: string): Promise<GuaribasPackage> { packageName: string
): Promise<GuaribasPackage> {
return GuaribasPackage.create({ return GuaribasPackage.create({
packageName: packageName, packageName: packageName,
instanceId: instanceId instanceId: instanceId
}); });
} }
deployTheme(localPath: string) { 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(() => {
// }) // })
// .catch(err => { // .catch(err => {
// var gberr = GBError.create( // var gberr = GBError.create(
@ -263,7 +266,6 @@ export class GBDeployer {
} }
async deployPackageFromLocalPath(localPath: string) { async deployPackageFromLocalPath(localPath: string) {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
switch (packageType) { switch (packageType) {
@ -279,7 +281,6 @@ export class GBDeployer {
return service.deployKb(this.core, this, localPath); return service.deployKb(this.core, this, localPath);
case ".gbui": case ".gbui":
break; break;
default: default:
@ -291,11 +292,7 @@ export class GBDeployer {
} }
} }
async undeployPackageFromLocalPath( async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) {
instance: IGBInstance,
localPath: string
) {
let packageType = Path.extname(localPath); let packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); let packageName = Path.basename(localPath);
@ -315,7 +312,6 @@ export class GBDeployer {
return service.undeployKbFromStorage(instance, p.packageId); return service.undeployKbFromStorage(instance, p.packageId);
case ".gbui": case ".gbui":
break; break;
default: default:
@ -327,8 +323,10 @@ export class GBDeployer {
} }
} }
async getPackageByName(instanceId: number, packageName: string): async getPackageByName(
Promise<GuaribasPackage> { instanceId: number,
packageName: string
): Promise<GuaribasPackage> {
var where = { packageName: packageName, instanceId: instanceId }; var where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({ return GuaribasPackage.findOne({
where: where where: where
@ -341,7 +339,6 @@ export class GBDeployer {
* *
*/ */
async scanBootPackage() { async scanBootPackage() {
const deployFolder = "deploy"; const deployFolder = "deploy";
let bootPackage = GBConfigService.get("BOOT_PACKAGE"); let bootPackage = GBConfigService.get("BOOT_PACKAGE");

View file

@ -32,42 +32,44 @@
"use strict"; "use strict";
const { TextPrompt } = require("botbuilder-dialogs"); const { TextPrompt } = require("botbuilder-dialogs");
const UrlJoin = require("url-join"); const UrlJoin = require("url-join");
const express = require("express"); const express = require("express");
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
import { BotFrameworkAdapter, BotStateSet, ConversationState, MemoryStorage, UserState } from "botbuilder"; import {
import { LanguageTranslator, LocaleConverter } from "botbuilder-ai"; BotFrameworkAdapter,
BotStateSet,
ConversationState,
MemoryStorage,
UserState
} from "botbuilder";
import { GBCoreService } from "./GBCoreService"; import { GBCoreService } from "./GBCoreService";
import { GBConversationalService } from "./GBConversationalService"; import { GBConversationalService } from "./GBConversationalService";
import { GBConfigService } from "./GBConfigService";
import * as request from "request-promise-native"; import * as request from "request-promise-native";
import { GBMinInstance, IGBCoreService, IGBInstance, IGBPackage, GBError } from "botlib"; import {
import { GBServiceCallback } from "botlib"; GBMinInstance,
IGBPackage,
} from "botlib";
import { GBAnalyticsPackage } from "../../analytics.gblib"; import { GBAnalyticsPackage } from "../../analytics.gblib";
import { GBCorePackage } from "../../core.gbapp"; import { GBCorePackage } from "../../core.gbapp";
import { GBKBPackage } from '../../kb.gbapp'; import { GBKBPackage } from "../../kb.gbapp";
import { GBDeployer } from './GBDeployer'; import { GBDeployer } from "./GBDeployer";
import { GBSecurityPackage } from '../../security.gblib'; import { GBSecurityPackage } from "../../security.gblib";
import { GBAdminPackage } from './../../admin.gbapp/index'; import { GBAdminPackage } from "./../../admin.gbapp/index";
import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp"; import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp";
import { GBWhatsappPackage } from "../../whatsapp.gblib"; import { GBWhatsappPackage } from "../../whatsapp.gblib";
/** Minimal service layer for a bot. */ /** Minimal service layer for a bot. */
export class GBMinService { export class GBMinService {
core: GBCoreService; core: GBCoreService;
conversationalService: GBConversationalService; conversationalService: GBConversationalService;
deployer: GBDeployer; deployer: GBDeployer;
corePackage = "core.gbai"; corePackage = "core.gbai";
/** /**
* Static initialization of minimal instance. * Static initialization of minimal instance.
* *
@ -83,19 +85,21 @@ export class GBMinService {
this.deployer = deployer; 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 server An HTTP server.
* @param appPackages List of loaded .gbapp associated with this instance. * @param appPackages List of loaded .gbapp associated with this instance.
* *
* @return Loaded minimal bot instance. * @return Loaded minimal bot instance.
* *
* */ * */
async buildMin(server: any, appPackages: Array<IGBPackage>): Promise<GBMinInstance> { async buildMin(
server: any,
appPackages: Array<IGBPackage>
): Promise<GBMinInstance> {
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
let uiPackage = "default.gbui"; let uiPackage = "default.gbui";
@ -104,94 +108,101 @@ export class GBMinService {
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) 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(); 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 server.get("/instances/:botId", (req, res) => {
// instance information stored on server. (async () => {
// Returns the instance object to clients requesting bot info.
server.get("/instances/:botId", (req, res) => { let botId = req.params.botId;
(async () => { 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; // Build bot adapter.
let instance = await this.core.loadInstance(botId);
if (instance) {
let speechToken = await this.getSTSToken(instance); var { min, adapter, conversationState } = await this.buildBotAdapter(
instance
);
res.send( // Call the loadBot context.activity for all packages.
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);
}
})()
});
// 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 uiUrl = `/${instance.botId}`;
server.use(
let url = `/api/messages/${instance.botId}`; uiUrl,
server.post(url, async (req, res) => { express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
return this.receiver(adapter, req, res, conversationState, min, );
instance, appPackages); logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`);
});
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();
}));
// 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) { private async buildBotAdapter(instance: any) {
let adapter = new BotFrameworkAdapter({ let adapter = new BotFrameworkAdapter({
appId: instance.marketplaceId, appId: instance.marketplaceId,
appPassword: instance.marketplacePassword appPassword: instance.marketplacePassword
@ -203,7 +214,7 @@ export class GBMinService {
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;
@ -211,27 +222,34 @@ export class GBMinService {
min.core = this.core; min.core = this.core;
min.conversationalService = this.conversationalService; min.conversationalService = this.conversationalService;
min.instance = await this.core.loadInstance(min.botId); min.instance = await this.core.loadInstance(min.botId);
min.dialogs.add('textPrompt', new TextPrompt()); min.dialogs.add("textPrompt", new TextPrompt());
return { min, adapter, conversationState }; return { min, adapter, conversationState };
} }
private invokeLoadBot(appPackages: any[], min: any, server: any) { private invokeLoadBot(appPackages: any[], min: any, server: any) {
appPackages.forEach(e => { appPackages.forEach(e => {
e.sysPackages = new Array<IGBPackage>(); e.sysPackages = new Array<IGBPackage>();
[GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, [
GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { GBAdminPackage,
logger.info(`Loading sys package: ${sysPackage.name}...`); GBAnalyticsPackage,
let p = Object.create(sysPackage.prototype) as IGBPackage; GBCorePackage,
p.loadBot(min); GBSecurityPackage,
e.sysPackages.push(p); GBKBPackage,
if (sysPackage.name === "GBWhatsappPackage") { GBCustomerSatisfactionPackage,
let url = "/instances/:botId/whatsapp"; GBWhatsappPackage
server.post(url, (req, res) => { ].forEach(sysPackage => {
p["channel"].received(req, res); logger.info(`Loading sys package: ${sysPackage.name}...`);
}); let p = Object.create(sysPackage.prototype) as IGBPackage;
} p.loadBot(min);
}, this); 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); e.loadBot(min);
}, this); }, this);
} }
@ -239,11 +257,16 @@ export class GBMinService {
/** /**
* Bot Service hook method. * Bot Service hook method.
*/ */
private receiver(adapter: BotFrameworkAdapter, req: any, res: any, conversationState private receiver(
: ConversationState, min: any, instance: any, appPackages: any[]) { adapter: BotFrameworkAdapter,
req: any,
return adapter.processActivity(req, res, async (context) => { res: any,
conversationState: ConversationState,
min: any,
instance: any,
appPackages: any[]
) {
return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context); const state = conversationState.get(context);
const dc = min.dialogs.createContext(context, state); const dc = min.dialogs.createContext(context, state);
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
@ -252,86 +275,76 @@ export class GBMinService {
instanceId: instance.instanceId, instanceId: instance.instanceId,
botId: instance.botId, botId: instance.botId,
theme: instance.theme, theme: instance.theme,
secret: instance.webchatKey, secret: instance.webchatKey
}); });
user.loaded = true; user.loaded = true;
user.subjects = []; 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}, ConversationID: ${context.activity.conversation.id},
Name: ${context.activity.name}, Text: ${context.activity.text}.`); Name: ${context.activity.name}, Text: ${
if (context.activity.type === "conversationUpdate" && context.activity.text
context.activity.membersAdded.length > 0) { }.`);
if (
context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0
) {
let member = context.activity.membersAdded[0]; let member = context.activity.membersAdded[0];
if (member.name === "GeneralBots") { if (member.name === "GeneralBots") {
logger.info(`Bot added to conversation, starting chat...`); logger.info(`Bot added to conversation, starting chat...`);
appPackages.forEach(e => { appPackages.forEach(e => {
e.onNewSession(min, dc); e.onNewSession(min, dc);
}); });
await dc.begin('/'); await dc.begin("/");
} } else {
else {
logger.info(`Member added to conversation: ${member.name}`); 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 // Check to see if anyone replied. If not then start echo dialog
if (context.activity.text === "admin") { if (context.activity.text === "admin") {
await dc.begin("/admin"); await dc.begin("/admin");
} } else {
else {
await dc.continue(); await dc.continue();
} }
} else if (context.activity.type === "event") {
}
else if (context.activity.type === 'event') {
if (context.activity.name === "whoAmI") { if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI"); await dc.begin("/whoAmI");
} } else if (context.activity.name === "showSubjects") {
else if (context.activity.name === "showSubjects") {
await dc.begin("/menu"); await dc.begin("/menu");
} } else if (context.activity.name === "giveFeedback") {
else if (context.activity.name === "giveFeedback") {
await dc.begin("/feedback", { await dc.begin("/feedback", {
fromMenu: true fromMenu: true
}); });
} } else if (context.activity.name === "showFAQ") {
else if (context.activity.name === "showFAQ") {
await dc.begin("/faq"); await dc.begin("/faq");
} } else if (context.activity.name === "ask") {
else if (context.activity.name === "ask") {
await dc.begin("/answer", { await dc.begin("/answer", {
query: (context.activity as any).data, query: (context.activity as any).data,
fromFaq: true fromFaq: true
}); });
} } else if (context.activity.name === "quality") {
else if (context.activity.name === "quality") {
await dc.begin("/quality", { await dc.begin("/quality", {
// TODO: score: context.activity.data // TODO: score: context.activity.data
}); });
} } else {
else {
await dc.continue(); await dc.continue();
} }
} }
}); });
} }
/** /**
* Get Webchat key from Bot Service. * Get Webchat key from Bot Service.
* *
* @param instance The Bot instance. * @param instance The Bot instance.
* *
*/ */
async getWebchatToken(instance: any) { async getWebchatToken(instance: any) {
let options = { let options = {
url: url: "https://directline.botframework.com/v3/directline/tokens/generate",
"https://directline.botframework.com/v3/directline/tokens/generate",
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Bearer ${instance.webchatKey}` Authorization: `Bearer ${instance.webchatKey}`
@ -350,17 +363,15 @@ export class GBMinService {
/** /**
* Gets a Speech to Text / Text to Speech token from the provider. * Gets a Speech to Text / Text to Speech token from the provider.
* *
* @param instance The general bot instance. * @param instance The general bot instance.
* *
*/ */
async getSTSToken(instance: any) { async getSTSToken(instance: any) {
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
let options = { let options = {
url: url: "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
"https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
method: "POST", method: "POST",
headers: { headers: {
"Ocp-Apim-Subscription-Key": instance.speechKey "Ocp-Apim-Subscription-Key": instance.speechKey
@ -375,4 +386,4 @@ export class GBMinService {
return Promise.reject(msg); return Promise.reject(msg);
} }
} }
} }

View file

@ -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);
});
});
});

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,14 +30,13 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict"
import { CSService } from '../services/CSService'
import { CSService } from '../services/CSService'; import { AzureText } from "pragmatismo-io-framework"
import { AzureText } from "pragmatismo-io-framework"; import { GBMinInstance } from "botlib"
import { GBMinInstance } from "botlib"; import { IGBDialog } from "botlib"
import { IGBDialog } from "botlib"; import { BotAdapter } from 'botbuilder'
import { BotAdapter } from 'botbuilder';
export class FeedbackDialog extends IGBDialog { export class FeedbackDialog extends IGBDialog {
@ -49,7 +48,7 @@ export class FeedbackDialog extends IGBDialog {
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new CSService(); const service = new CSService()
min.dialogs.add("/feedbackNumber", [ min.dialogs.add("/feedbackNumber", [
async (dc) => { async (dc) => {
@ -57,17 +56,17 @@ export class FeedbackDialog extends IGBDialog {
"O que achou do meu atendimento, de 1 a 5?", "O que achou do meu atendimento, de 1 a 5?",
"Qual a nota do meu atendimento?", "Qual a nota do meu atendimento?",
"Como define meu atendimento numa escala de 1 a 5?" "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) => { 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)
await service.updateConversationRate(user.conversation, rate); await service.updateConversationRate(user.conversation, rate)
let messages = ["Obrigado!", "Obrigado por responder."]; let messages = ["Obrigado!", "Obrigado por responder."]
await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. await dc.context.sendActivity(messages[0]) // TODO: Handle rnd.
} }
]); ])
min.dialogs.add("/feedback", [ min.dialogs.add("/feedback", [
async (dc, args) => { async (dc, args) => {
@ -75,27 +74,30 @@ export class FeedbackDialog extends IGBDialog {
let messages = [ let messages = [
"Sugestões melhoram muito minha qualidade...", "Sugestões melhoram muito minha qualidade...",
"Obrigado pela sua iniciativa de sugestão." "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 = [ let messages = [
"O que achou do meu atendimento?", "O que achou do meu atendimento?",
"Como foi meu atendimento?", "Como foi meu atendimento?",
"Gostaria de dizer algo sobre meu atendimento?" "Gostaria de dizer algo sobre meu atendimento?"
]; ]
await dc.prompt('textPrompt', messages[0]); await dc.prompt('textPrompt', messages[0])
}, },
async (dc, value) => { 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) { 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 { } else {
await dc.context.sendActivity( await dc.context.sendActivity(
"Vamos registrar sua questão, obrigado pela sinceridade." "Vamos registrar sua questão, obrigado pela sinceridade."
); )
} }
await dc.replace('/ask', { isReturning: true }); await dc.replace('/ask', { isReturning: true })
}]); }])
} }
} }

View file

@ -35,47 +35,33 @@ import { GuaribasConversation } from '../../analytics.gblib/models';
export class CSService { export class CSService {
resolveQuestionAlternate( async resolveQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string): Promise<GuaribasQuestionAlternate> { questionTyped: string): Promise<GuaribasQuestionAlternate> {
return new Promise<GuaribasQuestionAlternate>(
(resolve, reject) => { return GuaribasQuestionAlternate.findOne({
GuaribasQuestionAlternate.findOne({ where: {
where: { instanceId: instanceId,
instanceId: instanceId, questionTyped: questionTyped
questionTyped: questionTyped }
} })
}).then((value: GuaribasQuestionAlternate) => {
resolve(value);
}).error(reason => reject(reason));
});
} }
insertQuestionAlternate( async insertQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string, questionTyped: string,
questionText: string): Promise<GuaribasQuestionAlternate> { questionText: string): Promise<GuaribasQuestionAlternate> {
return new Promise<GuaribasQuestionAlternate>( return GuaribasQuestionAlternate.create({
(resolve, reject) => { questionTyped: questionTyped,
GuaribasQuestionAlternate.create({ questionText: questionText
questionTyped: questionTyped, })
questionText: questionText
}).then(item => {
resolve(item);
}).error(reason => reject(reason));
});
} }
updateConversationRate( async updateConversationRate(
conversation: GuaribasConversation, conversation: GuaribasConversation,
rate: number rate: number
): Promise<GuaribasConversation> { ): Promise<GuaribasConversation> {
return new Promise<GuaribasConversation>( conversation.rate = rate;
(resolve, reject) => { return conversation.save()
conversation.rate = rate;
conversation.save().then((value: GuaribasConversation) => {
resolve(conversation);
}).error(reason => reject(reason));
});
} }
} }

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,16 +30,16 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict"
import { IGBDialog } from "botlib"; import { IGBDialog } from "botlib"
import { AzureText } from "pragmatismo-io-framework"; import { AzureText } from "pragmatismo-io-framework"
import { GBMinInstance } from "botlib"; import { GBMinInstance } from "botlib"
import { KBService } from './../services/KBService'; import { KBService } from './../services/KBService'
import { BotAdapter } from "botbuilder"; import { BotAdapter } from "botbuilder"
import { LuisRecognizer } from "botbuilder-ai"; 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 {
/** /**
@ -50,42 +50,67 @@ export class AskDialog extends IGBDialog {
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize); const service = new KBService(min.core.sequelize)
const model = new LuisRecognizer({ const model = new LuisRecognizer({
appId: min.instance.nlpAppId, appId: min.instance.nlpAppId,
subscriptionKey: min.instance.nlpSubscriptionKey, subscriptionKey: min.instance.nlpSubscriptionKey,
serviceEndpoint: min.instance.nlpServerUrl serviceEndpoint: min.instance.nlpServerUrl
}); })
min.dialogs.add("/answer", [ min.dialogs.add("/answer", [
async (dc, args) => { async (dc, args) => {
// Initialize values. // Initialize values.
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context)
let text = args.query; let text = args.query
if (!text) { if (!text) {
throw new Error(`/answer being called with no args.query 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. // Stops any content on projector.
await min.conversationalService.sendEvent(dc, "stop", null); await min.conversationalService.sendEvent(dc, "stop", null)
// Handle extra text from FAQ. // Handle extra text from FAQ.
if (args && args.query) { if (args && args.query) {
text = args.query; text = args.query
} else if (args && args.fromFaq) { } else if (args && args.fromFaq) {
let messages = [ let messages = [
`Ótima escolha, procurando resposta para sua questão...`, `Ótima escolha, procurando resposta para sua questão...`,
`Pesquisando sobre o termo...`, `Pesquisando sobre o termo...`,
`Aguarde, por favor, enquanto acho sua resposta...` `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. // Spells check the input text before sending Search or NLP.
@ -93,23 +118,22 @@ export class AskDialog extends IGBDialog {
if (min.instance.spellcheckerKey) { if (min.instance.spellcheckerKey) {
let data = await AzureText.getSpelledText( let data = await AzureText.getSpelledText(
min.instance.spellcheckerKey, min.instance.spellcheckerKey,
text); text)
if (data != text) { if (data != text) {
logger.info(`Spelling corrected: ${data}`); logger.info(`Spelling corrected: ${data}`)
text = data; text = data
} }
} }
// Searches KB for the first time. // Searches KB for the first time.
user.lastQuestion = text; user.lastQuestion = text
let resultsA = await service.ask( let resultsA = await service.ask(
min.instance, min.instance,
text, text,
min.instance.searchScore, min.instance.searchScore,
user.subjects); user.subjects)
// If there is some result, answer immediately. // If there is some result, answer immediately.
@ -117,23 +141,23 @@ export class AskDialog extends IGBDialog {
// Saves some context info. // Saves some context info.
user.isAsking = false; user.isAsking = false
user.lastQuestionId = resultsA.questionId; user.lastQuestionId = resultsA.questionId
// Sends the answer to all outputs, including projector. // 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. // Goes to ask loop, again.
await dc.replace("/ask", { isReturning: true }); await dc.replace("/ask", { isReturning: true })
} else { } else {
// Second time running Search, now with no filter. // Second time running Search, now with no filter.
let resultsB = await service.ask(min.instance, text, let resultsB = await service.ask(min.instance, text,
min.instance.searchScore, null); min.instance.searchScore, null)
// If there is some result, answer immediately. // If there is some result, answer immediately.
@ -141,9 +165,9 @@ export class AskDialog extends IGBDialog {
// Saves some context info. // Saves some context info.
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context)
user.isAsking = false; user.isAsking = false
user.lastQuestionId = resultsB.questionId; user.lastQuestionId = resultsB.questionId
// Informs user that a broader search will be used. // Informs user that a broader search will be used.
@ -151,53 +175,53 @@ export class AskDialog extends IGBDialog {
let subjectText = let subjectText =
`${KBService.getSubjectItemsSeparatedBySpaces( `${KBService.getSubjectItemsSeparatedBySpaces(
user.subjects user.subjects
)}`; )}`
let messages = [ let messages = [
`Respondendo nao apenas sobre ${subjectText}... `, `Respondendo nao apenas sobre ${subjectText}... `,
`Respondendo de modo mais abrangente...`, `Respondendo de modo mais abrangente...`,
`Vou te responder de modo mais abrangente... `Vou te responder de modo mais abrangente...
Não apenas sobre ${subjectText}` 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. // Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, dc, resultsB.answer); await service.sendAnswer(min.conversationalService, dc, resultsB.answer)
await dc.replace("/ask", { isReturning: true }); await dc.replace("/ask", { isReturning: true })
} else { } else {
let data = await min.conversationalService.runNLP(dc, min, text); let data = await min.conversationalService.runNLP(dc, min, text)
if (!data) { if (!data) {
let messages = [ let messages = [
"Desculpe-me, não encontrei nada a respeito.", "Desculpe-me, não encontrei nada a respeito.",
"Lamento... Não encontrei nada sobre isso. Vamos tentar novamente?", "Lamento... Não encontrei nada sobre isso. Vamos tentar novamente?",
"Desculpe-me, não achei nada parecido. Poderia tentar escrever de outra forma?" "Desculpe-me, não achei nada parecido. Poderia tentar escrever de outra forma?"
]; ]
await dc.context.sendActivity(messages[0]); // TODO: Handle rnd. await dc.context.sendActivity(messages[0]) // TODO: Handle rnd.
await dc.replace("/ask", { isReturning: true }); await dc.replace("/ask", { isReturning: true })
} }
} }
} }
} }
]); ])
min.dialogs.add("/ask", [ min.dialogs.add("/ask", [
async (dc, args) => { async (dc, args) => {
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context)
user.isAsking = true; user.isAsking = true
if (!user.subjects) { if (!user.subjects) {
user.subjects = []; user.subjects = []
} }
let text = []; let text = []
if (user.subjects.length > 0) { if (user.subjects.length > 0) {
text = [ text = [
`Faça sua pergunta...`, `Faça sua pergunta...`,
`Pode perguntar sobre o assunto em questão... `, `Pode perguntar sobre o assunto em questão... `,
`Qual a pergunta?` `Qual a pergunta?`
]; ]
} }
if (args && args.isReturning) { if (args && args.isReturning) {
@ -205,16 +229,16 @@ export class AskDialog extends IGBDialog {
"Sobre o que mais posso ajudar?", "Sobre o que mais posso ajudar?",
"Então, posso ajudar em algo a mais?", "Então, posso ajudar em algo a mais?",
"Deseja fazer outra pergunta?" "Deseja fazer outra pergunta?"
]; ]
} }
if (text.length > 0) { if (text.length > 0) {
await dc.prompt('textPrompt', text[0]); await dc.prompt('textPrompt', text[0])
} }
}, },
async (dc, value) => { async (dc, value) => {
await dc.endAll(); await dc.endAll()
await dc.begin("/answer", { query: value }); await dc.begin("/answer", { query: value })
} }
]); ])
} }
} }

View file

@ -155,13 +155,21 @@ export class KBService {
let value = await this.getAnswerById( let value = await this.getAnswerById(
instance.instanceId, instance.instanceId,
results[0].answerId) 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 { } else {
let data = await this.getAnswerByText(instance.instanceId, query) let data = await this.getAnswerByText(instance.instanceId, query)
return Promise.resolve( if (data) {
{ answer: data.answer, questionId: data.question.questionId } return Promise.resolve(
) { answer: data.answer, questionId: data.question.questionId })
} else {
return Promise.resolve({ answer: null, questionId: 0 })
}
} }
} }
catch (reason) { catch (reason) {
@ -461,7 +469,7 @@ export class KBService {
"A resposta está na tela...", "A resposta está na tela...",
"Veja a resposta na tela..." "Veja a resposta na tela..."
] ]
await dc.context.sendActivity(messages[0]) // TODO: Handle rnd. await dc.context.sendActivity(messages[0]) // TODO: Handle rnd.
var html = answer.content var html = answer.content

37
tslint.json Normal file
View file

@ -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+\\*"}]
}
}