Added Azure AD administration routines.

This commit is contained in:
Rodrigo Rodriguez 2018-09-16 17:00:17 -03:00
parent e7a7fcedbf
commit c03228dbbe
18 changed files with 617 additions and 374 deletions

View file

@ -30,33 +30,33 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
const UrlJoin = require("url-join")
import { AzureSearch } from "pragmatismo-io-framework"
import { GBMinInstance } from "botlib"
import { IGBDialog } from "botlib"
import { GBDeployer } from '../../core.gbapp/services/GBDeployer'
import { GBImporter } from '../../core.gbapp/services/GBImporter'
import { GBConfigService } from '../../core.gbapp/services/GBConfigService'
import { KBService } from './../../kb.gbapp/services/KBService'
import { BotAdapter } from "botbuilder"
const UrlJoin = require("url-join");
import { AzureSearch } from "pragmatismo-io-framework";
import { GBMinInstance } from "botlib";
import { IGBDialog } from "botlib";
import { GBDeployer } from "../../core.gbapp/services/GBDeployer";
import { GBImporter } from "../../core.gbapp/services/GBImporter";
import { GBConfigService } from "../../core.gbapp/services/GBConfigService";
import { KBService } from "./../../kb.gbapp/services/KBService";
import { BotAdapter } from "botbuilder";
import { GBAdminService } from "../services/GBAdminService";
/**
* Dialogs for administration tasks.
*/
export class AdminDialog extends IGBDialog {
static async undeployPackageCommand(text: any, min: GBMinInstance, dc) {
let packageName = text.split(" ")[1]
let importer = new GBImporter(min.core)
let deployer = new GBDeployer(min.core, importer)
dc.context.sendActivity(`Undeploying package ${packageName}...`)
let packageName = text.split(" ")[1];
let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer);
dc.context.sendActivity(`Undeploying package ${packageName}...`);
await deployer.undeployPackageFromLocalPath(
min.instance,
UrlJoin("deploy", packageName))
dc.context.sendActivity(`Package ${packageName} undeployed...`)
UrlJoin("deploy", packageName)
);
dc.context.sendActivity(`Package ${packageName} undeployed...`);
}
static async deployPackageCommand(
@ -65,11 +65,17 @@ export class AdminDialog extends IGBDialog {
deployer: GBDeployer,
min: GBMinInstance
) {
let packageName = text.split(" ")[1]
await dc.context.sendActivity(`Deploying package ${packageName}... (It may take a few seconds)`)
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH")
await deployer.deployPackageFromLocalPath(UrlJoin(additionalPath, packageName))
await dc.context.sendActivity(`Package ${packageName} deployed... Please run rebuildIndex command.`)
let packageName = text.split(" ")[1];
await dc.context.sendActivity(
`Deploying package ${packageName}... (It may take a few seconds)`
);
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
await deployer.deployPackageFromLocalPath(
UrlJoin(additionalPath, packageName)
);
await dc.context.sendActivity(
`Package ${packageName} deployed... Please run rebuildIndex command.`
);
}
static async rebuildIndexCommand(min: GBMinInstance, dc) {
@ -78,95 +84,120 @@ export class AdminDialog extends IGBDialog {
min.instance.searchHost,
min.instance.searchIndex,
min.instance.searchIndexer
)
dc.context.sendActivity("Rebuilding index...")
await search.deleteIndex()
let kbService = new KBService(min.core.sequelize)
await search.createIndex(kbService.getSearchSchema(min.instance.searchIndex), "gb")
await dc.context.sendActivity("Index rebuilt.")
);
dc.context.sendActivity("Rebuilding index...");
await search.deleteIndex();
let kbService = new KBService(min.core.sequelize);
await search.createIndex(
kbService.getSearchSchema(min.instance.searchIndex),
"gb"
);
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)
let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer);
min.dialogs.add("/admin", [
async dc => {
await AdminDialog.refreshAdminToken(min, dc);
// await dc.context.sendActivity(
// `Deploying package ... (It may take a few seconds)`
// );
// await AdminDialog.deployPackageCommand(
// "deployPackage ProjectOnline.gbkb",
// dc,
// deployer,
// min
// );
await dc.endAll();
}
]);
async (dc) => {
await dc.context.sendActivity(`Deploying package ... (It may take a few seconds)`)
await AdminDialog.deployPackageCommand("deployPackage ProjectOnline.gbkb", dc, deployer, min)
await dc.endAll()
}])
min.dialogs.add("/adminUpdateToken", [
async (dc, args, next) => {
await dc.endAll();
let service = new GBAdminService();
await service.saveValue("authenticatorToken", args.token)
await dc.context.sendActivity("Token has been updated.");
await dc.replace("/ask")
}
]);
min.dialogs.add("/admin1", [
async (dc, args) => {
const prompt = "Please, authenticate:"
await dc.prompt('textPrompt', prompt)
const prompt = "Please, authenticate:";
await dc.prompt("textPrompt", prompt);
},
async (dc, value) => {
let text = value
const user = min.userState.get(dc.context)
let text = value;
const user = min.userState.get(dc.context);
if (
!user.authenticated ||
text === GBConfigService.get("ADMIN_PASS")
) {
user.authenticated = true
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?")
);
await dc.prompt("textPrompt", "Which task do you wanna run now?");
} else {
await dc.endAll()
await dc.endAll();
}
},
async (dc, value) => {
var text = value
const user = min.userState.get(dc.context)
var text = value;
const user = min.userState.get(dc.context);
if (text === "quit") {
user.authenticated = false
await dc.replace("/")
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 })
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 })
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 })
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 })
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 })
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 })
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...")
await AdminDialog.refreshAdminToken(min, dc);
}
}
])
]);
}
private static async refreshAdminToken(min: any, dc: any) {
let config = {
authenticatorTenant: min.instance.authenticatorTenant,
authenticatorClientID: min.instance.authenticatorClientID
};
await min.conversationalService.sendEvent(dc, "play", {
playerType: "login",
data: config
});
await dc.context.sendActivity("Update your Administrative token by Login...");
}
}

View file

@ -38,11 +38,15 @@ import { AdminDialog } from './dialogs/AdminDialog'
import { GBMinInstance, IGBPackage, IGBCoreService } from 'botlib'
import { Sequelize } from 'sequelize-typescript'
import { GuaribasAdmin } from './models/AdminModel';
export class GBAdminPackage implements IGBPackage {
sysPackages: IGBPackage[] = null
loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasAdmin
])
}
unloadPackage(core: IGBCoreService): void {

View file

@ -0,0 +1,61 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| 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";
import {
Table,
Column,
Model,
CreatedAt,
UpdatedAt,
} from "sequelize-typescript";
@Table
export class GuaribasAdmin extends Model<GuaribasAdmin>
{
@Column
key: string;
@Column
value: string;
@Column
@CreatedAt
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date;
}

View file

@ -0,0 +1,57 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| 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"
import { GuaribasAdmin } from "../models/AdminModel";
export class GBAdminService {
async saveValue(key: string, value: string): Promise<GuaribasAdmin> {
let options = { where: {} }
options.where = { key: key }
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
return admin.save()
}
async getValue(key: string) {
let options = { where: {} }
options.where = { key: key }
let obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value);
}
}

View file

@ -54,14 +54,15 @@ export class WelcomeDialog extends IGBDialog {
user.once = true;
var a = new Date();
const date = a.getHours();
var msg = 4;
date < 12
? Messages[locale].good_morning
: date < 18
? Messages[locale].good_evening
: Messages[locale].good_night;
var msg =
date < 12
? Messages[locale].good_morning
: date < 18
? Messages[locale].good_evening
: Messages[locale].good_night;
await dc.context.sendActivity(Messages[locale].hi(msg));
await dc.replace("/ask", { firstTime: true });
if (
dc.context.activity &&

View file

@ -30,14 +30,14 @@
| |
\*****************************************************************************/
"use strict"
"use strict";
import {
DataTypes,
DataTypeUUIDv4,
DataTypeDate,
DataTypeDecimal
} from "sequelize"
} from "sequelize";
import {
Sequelize,
@ -54,170 +54,214 @@ import {
DataType,
PrimaryKey,
AutoIncrement
} from "sequelize-typescript"
} from "sequelize-typescript";
import { IGBInstance } from "botlib"
import { IGBInstance } from "botlib";
@Table
export class GuaribasInstance extends Model<GuaribasInstance> implements IGBInstance {
export class GuaribasInstance extends Model<GuaribasInstance>
implements IGBInstance {
@PrimaryKey
@AutoIncrement
@Column
instanceId: number
@Column applicationPrincipal: string
instanceId: number;
@Column
whoAmIVideo: string
applicationPrincipal: string;
@Column botId: string
@Column
whoAmIVideo: string;
@Column title: string
@Column
botId: string;
@Column description: string
@Column
title: string;
@Column version: string
@Column
description: string;
@Column enabledAdmin: boolean
@Column
version: string;
@Column
enabledAdmin: boolean;
/* Services section on bot.json */
@Column engineName: string
@Column
engineName: string;
@Column marketplaceId: string
@Column
marketplaceId: string;
@Column textAnalyticsKey: string
@Column
textAnalyticsKey: string;
@Column textAnalyticsServerUrl: string
@Column
textAnalyticsServerUrl: string;
@Column marketplacePassword: string
@Column
marketplacePassword: string;
@Column webchatKey: string
@Column
webchatKey: string;
@Column whatsappBotKey: string
@Column
authenticatorTenant: string;
@Column
authenticatorSignUpSignInPolicy: string;
@Column
authenticatorClientID: string;
@Column whatsappServiceKey: string
@Column
whatsappBotKey: string;
@Column whatsappServiceNumber: string
@Column
whatsappServiceKey: string;
@Column whatsappServiceUrl: string
@Column
whatsappServiceNumber: string;
@Column whatsappServiceWebhookUrl: string
@Column
whatsappServiceUrl: string;
@Column speechKey: string
@Column
whatsappServiceWebhookUrl: string;
@Column spellcheckerKey: string
@Column
smsKey: string;
@Column theme: string
@Column
smsSecret: string;
@Column ui: string
@Column kb: string
@Column
smsServiceNumber: string;
@Column
nlpAppId: string
speechKey: string;
@Column
nlpSubscriptionKey: string
spellcheckerKey: string;
@Column
theme: string;
@Column
ui: string;
@Column
kb: string;
@Column
nlpAppId: string;
@Column
nlpSubscriptionKey: string;
@Column
@Column({ type: DataType.STRING(512) })
nlpServerUrl: string
nlpServerUrl: string;
@Column searchHost: string
@Column
searchHost: string;
@Column searchKey: string
@Column
searchKey: string;
@Column searchIndex: string
@Column
searchIndex: string;
@Column searchIndexer: string
@Column
searchIndexer: string;
/* Settings section of bot.json */
@Column(DataType.FLOAT) nlpVsSearch: number
@Column(DataType.FLOAT)
nlpVsSearch: number;
@Column(DataType.FLOAT) searchScore: number
@Column(DataType.FLOAT)
searchScore: number;
@Column(DataType.FLOAT) nlpScore: number
@Column(DataType.FLOAT)
nlpScore: number;
@Column
@CreatedAt
createdAt: Date
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date
updatedAt: Date;
}
@Table
export class GuaribasPackage extends Model<GuaribasPackage> {
@PrimaryKey
@AutoIncrement
@Column
packageId: number
packageId: number;
@Column
packageName: string
packageName: string;
@ForeignKey(() => GuaribasInstance)
@Column
instanceId: number
instanceId: number;
@BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance
instance: GuaribasInstance;
@Column
@CreatedAt
createdAt: Date
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date
updatedAt: Date;
}
@Table
export class GuaribasChannel extends Model<GuaribasChannel> {
@PrimaryKey
@AutoIncrement
@Column
channelId: number
channelId: number;
@Column title: string
@Column
title: string;
@Column
@CreatedAt
createdAt: Date
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date
updatedAt: Date;
}
@Table
export class GuaribasException extends Model<GuaribasException> {
@PrimaryKey
@AutoIncrement
@Column
exceptionId: number
exceptionId: number;
@Column message: string
@Column
message: string;
@ForeignKey(() => GuaribasInstance)
@Column
instanceId: number
instanceId: number;
@BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance
instance: GuaribasInstance;
@Column
@CreatedAt
createdAt: Date
createdAt: Date;
@Column
@UpdatedAt
updatedAt: Date
updatedAt: Date;
}

View file

@ -1,3 +1,4 @@
import { IGBInstance } from 'botlib';
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
@ -41,6 +42,8 @@ import { LuisRecognizer } from "botbuilder-ai";
import { MessageFactory } from "botbuilder";
import { Messages } from "../strings";
import { AzureText } from "pragmatismo-io-framework";
const Nexmo = require("nexmo");
export interface LanguagePickerSettings {
defaultLocale?: string;
@ -66,6 +69,19 @@ export class GBConversationalService implements IGBConversationalService {
return dc.context.sendActivity(msg);
}
async sendSms(min: GBMinInstance, mobile: string, text: string) : Promise<any> {
const nexmo = new Nexmo({
apiKey: min.instance.smsKey,
apiSecret: min.instance.smsSecret,
});
nexmo.message.sendSms(
min.instance.smsServiceNumber,
mobile,
text,
);
}
async routeNLP(dc: any, min: GBMinInstance, text: string): Promise<boolean> {
// Invokes LUIS.

View file

@ -37,6 +37,7 @@ import { Sequelize } from "sequelize-typescript"
import { GBConfigService } from "./GBConfigService"
import { IGBInstance, IGBCoreService } from "botlib"
import { GuaribasInstance } from "../models/GBModel"
import { GBAdminService } from "../../admin.gbapp/services/GBAdminService";
/**
* Core service layer.
@ -47,6 +48,11 @@ export class GBCoreService implements IGBCoreService {
*/
public sequelize: Sequelize
/**
* Administrative services.
*/
public adminService: GBAdminService
/**
* Allows filtering on SQL generated before send to the database.
*/
@ -72,6 +78,7 @@ export class GBCoreService implements IGBCoreService {
*/
constructor() {
this.dialect = GBConfigService.get("DATABASE_DIALECT")
this.adminService = new GBAdminService();
}
/**
@ -98,8 +105,8 @@ export class GBCoreService implements IGBCoreService {
let logging =
GBConfigService.get("DATABASE_LOGGING") === "true"
? (str: string) => {
logger.info(str)
}
logger.info(str)
}
: false
let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true"
@ -240,79 +247,38 @@ export class GBCoreService implements IGBCoreService {
}
async syncDatabaseStructure() {
return new Promise((resolve, reject) => {
if (GBConfigService.get("DATABASE_SYNC") === "true") {
const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"
const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"
logger.info("Syncing database...")
this.sequelize
.sync({
alter: alter,
force: force
})
.then(
value => {
logger.info("Database synced.")
resolve(value)
},
err => reject(err)
)
} else {
logger.info("Database synchronization is disabled.")
resolve()
}
})
if (GBConfigService.get("DATABASE_SYNC") === "true") {
const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"
const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"
logger.info("Syncing database...")
return this.sequelize.sync({
alter: alter,
force: force
});
} else {
let msg = "Database synchronization is disabled.";
logger.info(msg)
return Promise.reject(msg)
}
}
/**
* Loads all items to start several listeners.
*/
async loadInstances(): Promise<IGBInstance> {
return new Promise((resolve, reject) => {
GuaribasInstance.findAll({})
.then((items: IGBInstance[]) => {
if (!items) items = []
if (items.length == 0) {
resolve([])
} else {
resolve(items)
}
})
.catch(reason => {
if (reason.message.indexOf("no such table: GuaribasInstance") != -1) {
resolve([])
} else {
logger.info(`GuaribasServiceError: ${reason}`)
reject(reason)
}
})
})
return GuaribasInstance.findAll({});
}
/**
* Loads just one Bot instance.
*/
async loadInstance(botId: string): Promise<IGBInstance> {
return new Promise<IGBInstance>((resolve, reject) => {
let options = { where: {} }
let options = { where: {} }
if (botId != "[default]") {
options.where = { botId: botId }
}
if (botId != "[default]") {
options.where = { botId: botId }
}
GuaribasInstance.findOne(options)
.then((instance: IGBInstance) => {
if (instance) {
resolve(instance)
} else {
resolve(null)
}
})
.catch(err => {
logger.info(`GuaribasServiceError: ${err}`)
reject(err)
})
})
return GuaribasInstance.findOne(options);
}
}

View file

@ -134,7 +134,7 @@ export class GBDeployer {
appPackagesProcessed++
})
.catch(err => {
logger.info(`Error deploying App (.gbapp): ${e}: ${err}`)
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`)
appPackagesProcessed++
})
} else {
@ -145,14 +145,16 @@ export class GBDeployer {
WaitUntil()
.interval(1000)
.times(10)
.condition(function(cb) {
.condition(function (cb) {
logger.info(`Waiting for app package deployment...`)
cb(appPackagesProcessed == gbappPackages.length)
})
.done(function(result) {
logger.info(`App Package deployment done.`)
.done(function (result) {
logger.info(`App Package deployment done.`);
core.syncDatabaseStructure()
(async () => {
await core.syncDatabaseStructure()
})()
/** Deploys all .gbot files first. */
@ -179,7 +181,7 @@ export class GBDeployer {
server.use("/themes/" + filenameOnly, express.static(filename))
logger.info(
`Theme (.gbtheme) assets accessible at: ${"/themes/" +
filenameOnly}.`
filenameOnly}.`
)
/** Knowledge base for bots. */
@ -205,13 +207,13 @@ export class GBDeployer {
})
WaitUntil()
.interval(1000)
.interval(100)
.times(5)
.condition(function(cb) {
.condition(function (cb) {
logger.info(`Waiting for package deployment...`)
cb(totalPackages == generalPackages.length)
})
.done(function(result) {
.done(function (result) {
if (botPackages.length === 0) {
logger.info(
"The server is running with no bot instances, at least one .gbot file must be deployed."

View file

@ -121,7 +121,7 @@ export class GBMinService {
server.get("/instances/:botId", (req, res) => {
(async () => {
// Returns the instance object to clients requesting bot info.
let botId = req.params.botId
@ -136,7 +136,9 @@ export class GBMinService {
theme: instance.theme,
secret: instance.webchatKey, // TODO: Use token.
speechToken: speechToken,
conversationId: webchatToken.conversationId
conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant,
authenticatorClientID: instance.authenticatorClientID
})
)
} else {
@ -270,97 +272,105 @@ export class GBMinService {
appPackages: any[]
) {
return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context)
const dc = min.dialogs.createContext(context, state)
dc.context.activity.locale = "en-US"
const user = min.userState.get(dc.context)
if (!user.loaded) {
await min.conversationalService.sendEvent(dc, "loadInstance", {
instanceId: instance.instanceId,
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey
})
user.loaded = true
user.subjects = []
}
try {
const state = conversationState.get(context)
const dc = min.dialogs.createContext(context, state)
dc.context.activity.locale = "en-US"
const user = min.userState.get(dc.context)
logger.info(
`[RCV]: ${context.activity.type}, ChannelID: ${
if (!user.loaded) {
await min.conversationalService.sendEvent(dc, "loadInstance", {
instanceId: instance.instanceId,
botId: instance.botId,
theme: instance.theme,
secret: instance.webchatKey
})
user.loaded = true
user.subjects = []
}
logger.info(
`[RCV]: ${context.activity.type}, ChannelID: ${
context.activity.channelId
}, Name: ${context.activity.name}, Text: ${context.activity.text}.`
)
if (
context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0
) {
let member = context.activity.membersAdded[0]
if (member.name === "GeneralBots") {
logger.info(`Bot added to conversation, starting chat...`)
appPackages.forEach(e => {
e.onNewSession(min, dc)
})
}, Name: ${context.activity.name}, Text: ${context.activity.text}.`
)
if (
context.activity.type === "conversationUpdate" &&
context.activity.membersAdded.length > 0
) {
// Starts root dialog.
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("/")
// Processes the root dialog.
} else {
logger.info(`Member added to conversation: ${member.name}`)
}
// Processes messages.
} else if (context.activity.type === "message") {
// Checks for /admin request.
if (context.activity.text === "admin") {
await dc.begin("/admin")
// Checks for /menu JSON signature.
} else if (context.activity.text.startsWith("{\"title\"")) {
await dc.begin("/menu", {data:JSON.parse(context.activity.text)})
// Otherwise, continue to the active dialog in the stack.
} else {
await dc.continue()
}
// Processes events.
} else if (context.activity.type === "event") {
// Empties dialog stack before going to the target.
await dc.endAll()
if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI")
} else if (context.activity.name === "showSubjects") {
await dc.begin("/menu")
} else if (context.activity.name === "giveFeedback") {
await dc.begin("/feedback", {
fromMenu: true
})
} else if (context.activity.name === "showFAQ") {
await dc.begin("/faq")
} else if (context.activity.name === "ask") {
await dc.begin("/answer", {
query: (context.activity as any).data,
fromFaq: true
})
} else if (context.activity.name === "quality") {
await dc.begin("/quality", {
// TODO: score: context.activity.data
})
} else {
await dc.continue()
await dc.begin("/")
} else {
logger.info(`Member added to conversation: ${member.name}`)
}
// Processes messages.
} else if (context.activity.type === "message") {
// Checks for /admin request.
if (context.activity.text === "admin") {
await dc.begin("/admin")
// Checks for /menu JSON signature.
} else if (context.activity.text.startsWith("{\"title\"")) {
await dc.begin("/menu", { data: JSON.parse(context.activity.text) })
// Otherwise, continue to the active dialog in the stack.
} else {
await dc.continue()
}
// Processes events.
} else if (context.activity.type === "event") {
// Empties dialog stack before going to the target.
await dc.endAll()
if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI")
} else if (context.activity.name === "showSubjects") {
await dc.begin("/menu")
} else if (context.activity.name === "giveFeedback") {
await dc.begin("/feedback", {
fromMenu: true
})
} else if (context.activity.name === "showFAQ") {
await dc.begin("/faq")
} else if (context.activity.name === "ask") {
await dc.begin("/answer", {
query: (context.activity as any).data,
fromFaq: true
})
} else if (context.activity.name === "quality") {
await dc.begin("/quality", { score: (context.activity as any).data })
} else if (context.activity.name === "updateToken") {
let token = (context.activity as any).data
await dc.begin("/adminUpdateToken", { token: token })
} else {
await dc.continue()
}
}
} catch (error) {
let msg = `Error in main activity: ${error}.`
logger.error(msg)
}
})
}

View file

@ -55,4 +55,6 @@
<div id="root"></div>
</body>
</html>

View file

@ -45,6 +45,7 @@ import { SpeechSynthesizer } from "botframework-webchat/CognitiveServices";
import { SynthesisGender } from "botframework-webchat/CognitiveServices";
import { Chat } from "botframework-webchat";
import GBPowerBIPlayer from "./players/GBPowerBIPlayer.js";
import { UserAgentApplication } from "msal";
class GBUIApp extends React.Component {
constructor() {
@ -58,6 +59,22 @@ class GBUIApp extends React.Component {
};
}
sendToken(token) {
setTimeout(() => {
window.botConnection
.postActivity({
type: "event",
name: "updateToken",
data: token,
locale: "en-us",
textFormat: "plain",
timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" }
})
.subscribe(this.send("success"));
}, 400);
}
send(command) {
window.botConnection
.postActivity({
@ -93,8 +110,11 @@ class GBUIApp extends React.Component {
configureChat() {
var botId = window.location.href.split("/")[3];
if (botId.indexOf('#') != -1) {
botId = botId.split("#")[0];
}
if (!botId) {
if (!botId || botId == "") {
botId = "[default]";
}
@ -102,7 +122,7 @@ class GBUIApp extends React.Component {
.then(res => res.json())
.then(
result => {
this.setState({instanceClient:result});
this.setState({ instanceClient: result });
this.setupBotConnection();
},
error => {
@ -114,6 +134,38 @@ class GBUIApp extends React.Component {
);
}
authenticate() {
let _this_ = this;
let authority =
"https://login.microsoftonline.com/" +
this.state.instanceClient.authenticatorTenant;
let graphScopes = ["Directory.AccessAsUser.All"];
let userAgentApplication = new UserAgentApplication(
this.state.instanceClient.authenticatorClientID,
authority,
function (errorDesc, token, error, tokenType) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) {
_this_.sendToken(accessToken);
}, function (error) {
console.log(error);
})
}
);
if (!userAgentApplication.isCallback(window.location.hash) && window.parent === window && !window.opener) {
var user = userAgentApplication.getUser();
if (user) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) {
_this_.sendToken(accessToken);
}, function (error) {
console.log(error);
})
}
}
}
setupBotConnection() {
let _this_ = this;
window["botchatDebug"] = true;
@ -124,14 +176,13 @@ class GBUIApp extends React.Component {
botConnection.connectionStatus$.subscribe(connectionStatus => {
if (connectionStatus === ConnectionStatus.Online) {
_this_.setState({ botConnection: botConnection });
botConnection.postActivity({
type: "event",
value: "startGB",
from: this.getUser(),
name: "startGB"
});
_this_.setState({ botConnection: botConnection });
});
}
});
@ -145,8 +196,9 @@ class GBUIApp extends React.Component {
)
.subscribe(activity => {
_this_.setState({ instance: activity.value });
_this_.authenticate()
});
botConnection.activity$
.filter(activity => activity.type === "event" && activity.name === "stop")
.subscribe(activity => {
@ -168,7 +220,7 @@ class GBUIApp extends React.Component {
}
render() {
let playerComponent = "";
@ -224,7 +276,7 @@ class GBUIApp extends React.Component {
/>
);
break;
case "login":
case "login":
playerComponent = (
<GBLoginPlayer
app={this}
@ -246,7 +298,7 @@ class GBUIApp extends React.Component {
let speechOptions;
let chat = <div />;
let gbCss =<div />;
let gbCss = <div />;
let sideBar = (
@ -254,7 +306,7 @@ class GBUIApp extends React.Component {
<SidebarMenu chat={this.chat} instance={this.state.instance} />
</div>
);
if (this.state.botConnection && this.state.instance) {
let token = this.state.instanceClient.speechToken;
gbCss = <GBCss instance={this.state.instance} />;
@ -264,7 +316,7 @@ class GBUIApp extends React.Component {
resolve(token);
});
}
speechOptions = {
speechRecognizer: new SpeechRecognizer({
locale: "pt-br",
@ -301,7 +353,7 @@ class GBUIApp extends React.Component {
return (
<div>
{gbCss}
{gbCss}
{sideBar}
<div className="player">{playerComponent}</div>
{chat}

View file

@ -39,3 +39,4 @@ ReactDOM.render(
<GBUIApp head={document.getElementsByTagName("head")[0]} />,
document.getElementById("root")
);

View file

@ -31,68 +31,54 @@
\*****************************************************************************/
import React from "react";
import { Logger, LogLevel } from "msal";
import { UserAgentApplication } from "msal";
class GBLoginPlayer extends React.Component {
constructor(tenant) {
constructor() {
super();
this.state = {
token: "",
login: {}
};
}
login() {
let config = {
tenant: "pragmatismo.onmicrosoft.com", //"6ecb2a67-15af-4582-ab85-cc65096ce471",
signUpSignInPolicy: "b2c_1_susi",
clientID: '47cbaa05-dbb4-46f8-8608-da386c5131f1'}
let authority = "https://login.microsoftonline.com/tfp/" +
config.tenant + "/" +
config.signUpSignInPolicy;
let userAgentApplication = new UserAgentApplication(
config.clientID, authority,
function (errorDesc, token, error, tokenType) {
console.log(token);
}
doLogin(info) {
let logger = new Logger(
(logLevel, message, piiEnabled) => {
console.log(message);
},
{ level: LogLevel.Verbose }
);
let authority =
"https://login.microsoftonline.com/" +
this.state.login.authenticatorTenant;
let graphScopes = ["Directory.AccessAsUser.All"];
userAgentApplication.loginPopup(graphScopes).then(function (idToken) {
userAgentApplication.acquireTokenSilent(graphScopes).then(function (accessToken) {
console.log(accessToken);
}, function (error) {
userAgentApplication.acquireTokenPopup(graphScopes).then(function (accessToken) {
console.log(accessToken);
}, function (error) {
let userAgentApplication = new UserAgentApplication(
this.state.login.authenticatorClientID,
authority,
function (errorDesc, token, error, tokenType) {
if (error) {
console.log(error);
});
}
})
}, function (error) {
console.log(error);
});
userAgentApplication.loginRedirect(graphScopes);
}
play() {
play(data) {
this.setState({ login: data });
}
stop() {
this.setState({ login: [] });
}
render() {
return (
<button
value="Login"
onClick={this.login}
/>
);
return <button onClick={() => this.doLogin(this.state.login)}>Login</button>;
}
}

View file

@ -40,7 +40,6 @@ import { BotAdapter } from "botbuilder";
import { Messages } from "../strings";
import { LuisRecognizer } from "botbuilder-ai";
const logger = require("../../../src/logger");
export class AskDialog extends IGBDialog {
@ -69,7 +68,7 @@ export class AskDialog extends IGBDialog {
throw new Error(`/answer being called with no args.query text.`);
}
let locale = dc.context.activity.locale
let locale = dc.context.activity.locale;
// Stops any content on projector.
@ -163,7 +162,7 @@ export class AskDialog extends IGBDialog {
);
await dc.replace("/ask", { isReturning: true });
} else {
if (!(await min.conversationalService.runNLP(dc, min, text))) {
if (!(await min.conversationalService.routeNLP(dc, min, text))) {
await dc.context.sendActivity(Messages[locale].did_not_find);
await dc.replace("/ask", { isReturning: true });
}
@ -181,16 +180,21 @@ export class AskDialog extends IGBDialog {
user.subjects = [];
}
let text = [];
if (user.subjects.length > 0) {
text = Messages[locale].which_question;
}
if (args && args.isReturning) {
// Three forms of asking.
if (args.firstTime) {
text = Messages[locale].ask_first_time;
} else if (args && args.isReturning) {
text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) {
text = Messages[locale].which_question;
} else {
throw new Error("Invalid use of /ask");
}
if (text.length > 0) {
await dc.prompt("textPrompt", text[0]);
await dc.prompt("textPrompt", text);
}
},
async (dc, value) => {

View file

@ -13,7 +13,9 @@ export const Messages = {
`Vamos pesquisar sobre ${query}... O que deseja saber?`,
see_faq:
"Please take a look at the FAQ I've prepared for you. You can click on them to get the answer.",
will_answer_projector:"I'll answer on the projector to a better experience..."
will_answer_projector:
"I'll answer on the projector to a better experience...",
ask_first_time: "What are you looking for?"
},
"pt-BR": {
did_not_find: "Desculpe-me, não encontrei nada a respeito.",
@ -29,6 +31,8 @@ export const Messages = {
`Let's search about ${query}... What do you want to know?`,
see_faq:
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.",
will_answer_projector:"Vou te responder na tela para melhor visualização..."
will_answer_projector:
"Vou te responder na tela para melhor visualização...",
ask_first_time: "Sobre como eu poderia ajudar?"
}
};

View file

@ -42,20 +42,22 @@
"botlib": "^0.0.33",
"chokidar": "^2.0.4",
"csv-parse": "^3.0.0",
"dotenv-extended": "^2.2.0",
"dotenv-extended": "^2.3.0",
"express": "^4.16.3",
"express-promise-router": "^3.0.3",
"fs-extra": "^7.0.0",
"fs-walk": "^0.0.2",
"localize": "^0.4.7",
"marked": "^0.5.0",
"ms": "^2.1.1",
"nexmo": "^2.3.2",
"pragmatismo-io-framework": "^1.0.15",
"reflect-metadata": "^0.1.12",
"request-promise-native": "^1.0.5",
"sequelize": "^4.38.0",
"sequelize": "^4.38.1",
"sequelize-typescript": "^0.6.6",
"sqlite3": "^4.0.2",
"swagger-client": "^3.8.19",
"swagger-client": "^3.8.21",
"tedious": "^2.6.4",
"url-join": "^4.0.0",
"wait-until": "^0.0.2",

View file

@ -83,7 +83,6 @@ export class GBServer {
try {
logger.info(`Accepting connections on ${port}...`)
logger.info(`Starting instances...`)
// Reads basic configuration, initialize minimal services.
@ -93,10 +92,11 @@ export class GBServer {
// Boot a bot package if any.
logger.info(`Starting instances...`)
let deployer = new GBDeployer(core, new GBImporter(core))
// Build a minimal bot instance for each .gbot deployment.
let conversationalService = new GBConversationalService(core)
let minService = new GBMinService(core, conversationalService, deployer);