MinService Demonolithization.
This commit is contained in:
parent
8ca77a4a63
commit
3fdceda57c
5 changed files with 366 additions and 334 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { IGBPackage } from 'botlib';
|
||||||
/*****************************************************************************\
|
/*****************************************************************************\
|
||||||
| ( )_ _ |
|
| ( )_ _ |
|
||||||
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
|
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
|
||||||
|
@ -34,30 +35,196 @@
|
||||||
|
|
||||||
const logger = require("../../../src/logger");
|
const logger = require("../../../src/logger");
|
||||||
const Path = require("path");
|
const Path = require("path");
|
||||||
const _ = require("lodash");
|
|
||||||
const UrlJoin = require("url-join");
|
const UrlJoin = require("url-join");
|
||||||
|
const Fs = require("fs");
|
||||||
|
const WaitUntil = require("wait-until");
|
||||||
|
const express = require("express");
|
||||||
|
|
||||||
import { KBService } from './../../kb.gbapp/services/KBService';
|
import { KBService } from './../../kb.gbapp/services/KBService';
|
||||||
import { GBImporter } from "./GBImporter";
|
import { GBImporter } from "./GBImporter";
|
||||||
import { GBServiceCallback, 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 {
|
||||||
|
|
||||||
core: IGBCoreService;
|
core: IGBCoreService;
|
||||||
|
|
||||||
importer: GBImporter;
|
importer: GBImporter;
|
||||||
|
|
||||||
workDir: string = "./work";
|
workDir: string = "./work";
|
||||||
|
|
||||||
|
static deployFolder = "deploy";
|
||||||
|
|
||||||
constructor(core: IGBCoreService, importer: GBImporter) {
|
constructor(core: IGBCoreService, importer: GBImporter) {
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Performs package deployment in all .gbai or default.
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
public deployPackages(core: IGBCoreService, server: any, appPackages: Array<IGBPackage>) {
|
||||||
|
let _this = this;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let totalPackages = 0;
|
||||||
|
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
|
||||||
|
let paths = [GBDeployer.deployFolder];
|
||||||
|
if (additionalPath) {
|
||||||
|
paths = paths.concat(additionalPath.toLowerCase().split(";"));
|
||||||
|
}
|
||||||
|
let botPackages = new Array<string>();
|
||||||
|
let gbappPackages = new Array<string>();
|
||||||
|
let generalPackages = new Array<string>();
|
||||||
|
|
||||||
|
function doIt(path) {
|
||||||
|
const isDirectory = source => Fs.lstatSync(source).isDirectory()
|
||||||
|
const getDirectories = source =>
|
||||||
|
Fs.readdirSync(source).map(name => Path.join(source, name)).filter(isDirectory)
|
||||||
|
|
||||||
|
let dirs = getDirectories(path);
|
||||||
|
dirs.forEach(element => {
|
||||||
|
if (element.startsWith('.')) {
|
||||||
|
logger.info(`Ignoring ${element}...`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (element.endsWith('.gbot')) {
|
||||||
|
botPackages.push(element);
|
||||||
|
}
|
||||||
|
else if (element.endsWith('.gbapp')) {
|
||||||
|
gbappPackages.push(element);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
generalPackages.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`);
|
||||||
|
paths.forEach(e => {
|
||||||
|
logger.info(`Looking in: ${e}...`);
|
||||||
|
doIt(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Deploys all .gbapp files first. */
|
||||||
|
|
||||||
|
let appPackagesProcessed = 0;
|
||||||
|
|
||||||
|
gbappPackages.forEach(e => {
|
||||||
|
logger.info(`Deploying app: ${e}...`);
|
||||||
|
|
||||||
|
// Skips .gbapp inside deploy folder.
|
||||||
|
if (!e.startsWith('deploy')) {
|
||||||
|
import(e).then(m => {
|
||||||
|
let p = new m.Package();
|
||||||
|
p.loadPackage(core, core.sequelize);
|
||||||
|
appPackages.push(p);
|
||||||
|
logger.info(`App (.gbapp) deployed: ${e}.`);
|
||||||
|
appPackagesProcessed++;
|
||||||
|
}).catch(err => {
|
||||||
|
logger.info(`Error deploying App (.gbapp): ${e}: ${err}`);
|
||||||
|
appPackagesProcessed++;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
appPackagesProcessed++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
WaitUntil()
|
||||||
|
.interval(1000)
|
||||||
|
.times(10)
|
||||||
|
.condition(function (cb) {
|
||||||
|
logger.info(`Waiting for app package deployment...`);
|
||||||
|
cb(appPackagesProcessed == gbappPackages.length);
|
||||||
|
})
|
||||||
|
.done(function (result) {
|
||||||
|
logger.info(`App Package deployment done.`);
|
||||||
|
|
||||||
|
core.syncDatabaseStructure();
|
||||||
|
|
||||||
|
/** Deploys all .gbot files first. */
|
||||||
|
|
||||||
|
botPackages.forEach(e => {
|
||||||
|
logger.info(`Deploying bot: ${e}...`);
|
||||||
|
_this.deployBot(e);
|
||||||
|
logger.info(`Bot: ${e} deployed...`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Then all remaining generalPackages are loaded. */
|
||||||
|
|
||||||
|
generalPackages.forEach(filename => {
|
||||||
|
|
||||||
|
let filenameOnly = Path.basename(filename);
|
||||||
|
logger.info(`Deploying package: ${filename}...`);
|
||||||
|
|
||||||
|
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */
|
||||||
|
|
||||||
|
if (Path.extname(filename) === ".gbapp" || Path.extname(filename) === ".gblib") {
|
||||||
|
|
||||||
|
|
||||||
|
/** Themes for bots. */
|
||||||
|
|
||||||
|
} else if (Path.extname(filename) === ".gbtheme") {
|
||||||
|
server.use("/themes/" + filenameOnly, express.static(filename));
|
||||||
|
logger.info(`Theme (.gbtheme) assets accessible at: ${"/themes/" + filenameOnly}.`);
|
||||||
|
|
||||||
|
|
||||||
|
/** Knowledge base for bots. */
|
||||||
|
|
||||||
|
} else if (Path.extname(filename) === ".gbkb") {
|
||||||
|
server.use(
|
||||||
|
"/kb/" + filenameOnly + "/subjects",
|
||||||
|
express.static(UrlJoin(filename, "subjects"))
|
||||||
|
);
|
||||||
|
logger.info(`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (Path.extname(filename) === ".gbui" || filename.endsWith(".git")) {
|
||||||
|
// Already Handled
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unknown package format. */
|
||||||
|
|
||||||
|
else {
|
||||||
|
let err = new Error(`Package type not handled: ${filename}.`);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
totalPackages++;
|
||||||
|
});
|
||||||
|
|
||||||
|
WaitUntil()
|
||||||
|
.interval(1000)
|
||||||
|
.times(5)
|
||||||
|
.condition(function (cb) {
|
||||||
|
logger.info(`Waiting for package deployment...`);
|
||||||
|
cb(totalPackages == (generalPackages.length));
|
||||||
|
})
|
||||||
|
.done(function (result) {
|
||||||
|
if (botPackages.length === 0) {
|
||||||
|
logger.info(`The bot server is running empty: No bot instances have been found, at least one .gbot file must be deployed.`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info(`Package deployment done.`);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deploys a bot to the storage.
|
* Deploys a bot to the storage.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -32,16 +32,11 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const gBuilder = require("botbuilder");
|
|
||||||
const { TextPrompt } = require("botbuilder-dialogs");
|
const { TextPrompt } = require("botbuilder-dialogs");
|
||||||
const UrlJoin = require("url-join");
|
const UrlJoin = require("url-join");
|
||||||
const Path = require("path");
|
|
||||||
const Fs = require("fs");
|
|
||||||
const Url = require("url");
|
|
||||||
const logger = require("../../../src/logger");
|
|
||||||
const WaitUntil = require("wait-until");
|
|
||||||
const Walk = require("fs-walk");
|
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
|
const logger = require("../../../src/logger");
|
||||||
|
|
||||||
import { BotFrameworkAdapter, BotStateSet, ConversationState, MemoryStorage, UserState } from "botbuilder";
|
import { BotFrameworkAdapter, BotStateSet, ConversationState, MemoryStorage, UserState } from "botbuilder";
|
||||||
import { LanguageTranslator, LocaleConverter } from "botbuilder-ai";
|
import { LanguageTranslator, LocaleConverter } from "botbuilder-ai";
|
||||||
|
@ -69,7 +64,7 @@ export class GBMinService {
|
||||||
conversationalService: GBConversationalService;
|
conversationalService: GBConversationalService;
|
||||||
deployer: GBDeployer;
|
deployer: GBDeployer;
|
||||||
|
|
||||||
deployFolder = "deploy";
|
|
||||||
corePackage = "core.gbai";
|
corePackage = "core.gbai";
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,7 +83,16 @@ 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 appPackages List of loaded .gbapp associated with this instance.
|
||||||
|
*
|
||||||
|
* @return Loaded minimal bot instance.
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
async buildMin(server: any, appPackages: Array<IGBPackage>): Promise<GBMinInstance> {
|
async buildMin(server: any, appPackages: Array<IGBPackage>): Promise<GBMinInstance> {
|
||||||
|
|
||||||
|
@ -97,41 +101,22 @@ export class GBMinService {
|
||||||
let uiPackage = "default.gbui";
|
let uiPackage = "default.gbui";
|
||||||
server.use(
|
server.use(
|
||||||
"/",
|
"/",
|
||||||
express.static(UrlJoin(this.deployFolder, uiPackage, "build"))
|
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Loads all bot instances from storage.
|
// Loads all bot instances from storage and starting loading them.
|
||||||
|
|
||||||
let instances = await this.core.loadInstances();
|
let instances = await this.core.loadInstances();
|
||||||
|
|
||||||
// Gets the authorization key for each instance from Bot Service.
|
|
||||||
|
|
||||||
Promise.all(instances.map(async instance => {
|
Promise.all(instances.map(async instance => {
|
||||||
|
|
||||||
let options = {
|
// Gets the authorization key for each instance from Bot Service.
|
||||||
url:
|
|
||||||
"https://directline.botframework.com/v3/directline/tokens/generate",
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${instance.webchatKey}`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let responseObject: any;
|
let webchatToken = await this.getWebchatToken(instance);
|
||||||
|
|
||||||
try {
|
// Serves the bot information object via HTTP so clients can get
|
||||||
let response = await request(options);
|
|
||||||
responseObject = JSON.parse(response);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serves the bot information object via http so clients can get
|
|
||||||
// instance information stored on server.
|
// instance information stored on server.
|
||||||
|
|
||||||
server.get("/instances/:botId", (req, res) => {
|
server.get("/instances/:botId", (req, res) => {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
// Returns the instance object to clients requesting bot info.
|
// Returns the instance object to clients requesting bot info.
|
||||||
|
@ -140,24 +125,7 @@ export class GBMinService {
|
||||||
let instance = await this.core.loadInstance(botId);
|
let instance = await this.core.loadInstance(botId);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
|
|
||||||
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
|
let speechToken = await this.getSTSToken(instance);
|
||||||
|
|
||||||
let options = {
|
|
||||||
url:
|
|
||||||
"https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Ocp-Apim-Subscription-Key": instance.speechKey
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let response: any;
|
|
||||||
try {
|
|
||||||
response = await request(options);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error calling Speech to Text client. Error is: ${error}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(
|
res.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@ -165,8 +133,8 @@ export class GBMinService {
|
||||||
botId: botId,
|
botId: botId,
|
||||||
theme: instance.theme,
|
theme: instance.theme,
|
||||||
secret: instance.webchatKey, // TODO: Use token.
|
secret: instance.webchatKey, // TODO: Use token.
|
||||||
speechToken: response,
|
speechToken: speechToken,
|
||||||
conversationId: responseObject.conversationId
|
conversationId: webchatToken.conversationId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,147 +147,27 @@ export class GBMinService {
|
||||||
|
|
||||||
// Build bot adapter.
|
// Build bot adapter.
|
||||||
|
|
||||||
let adapter = new BotFrameworkAdapter({
|
var { min, adapter, conversationState } = await this.buildBotAdapter(instance);
|
||||||
appId: instance.marketplaceId,
|
|
||||||
appPassword: instance.marketplacePassword
|
|
||||||
});
|
|
||||||
const storage = new MemoryStorage();
|
|
||||||
const conversationState = new ConversationState(storage);
|
|
||||||
const userState = new UserState(storage);
|
|
||||||
adapter.use(new BotStateSet(conversationState, userState));
|
|
||||||
|
|
||||||
// The minimal bot is built here.
|
|
||||||
|
|
||||||
let min = new GBMinInstance();
|
|
||||||
min.botId = instance.botId;
|
|
||||||
min.bot = adapter;
|
|
||||||
min.userState = userState;
|
|
||||||
min.core = this.core;
|
|
||||||
min.conversationalService = this.conversationalService;
|
|
||||||
|
|
||||||
min.instance = await this.core.loadInstance(min.botId);
|
|
||||||
|
|
||||||
// Call the loadBot context.activity for all packages.
|
// Call the loadBot context.activity for all packages.
|
||||||
|
|
||||||
appPackages.forEach(e => {
|
this.invokeLoadBot(appPackages, min, server);
|
||||||
e.sysPackages = new Array<IGBPackage>();
|
|
||||||
[GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage,
|
|
||||||
GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => {
|
|
||||||
logger.info(`Loading sys package: ${sysPackage.name}...`);
|
|
||||||
let p = Object.create(sysPackage.prototype) as IGBPackage;
|
|
||||||
p.loadBot(min);
|
|
||||||
e.sysPackages.push(p);
|
|
||||||
|
|
||||||
if (sysPackage.name === "GBWhatsappPackage") {
|
|
||||||
let url = "/instances/:botId/whatsapp";
|
|
||||||
server.post(url, (req, res) => {
|
|
||||||
p["channel"].received(req, res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
e.loadBot(min);
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
|
|
||||||
// Serves individual URL for each bot conversational interface...
|
// Serves individual URL for each bot conversational interface...
|
||||||
|
|
||||||
let url = `/api/messages/${instance.botId}`;
|
let url = `/api/messages/${instance.botId}`;
|
||||||
logger.info(
|
server.post(url, async (req, res) => {
|
||||||
`GeneralBots(${instance.engineName}) listening on: ${url}.`
|
return this.receiver(adapter, req, res, conversationState, min,
|
||||||
);
|
instance, appPackages);
|
||||||
|
|
||||||
min.dialogs.add('textPrompt', new TextPrompt());
|
|
||||||
|
|
||||||
server.post(`/api/messages/${instance.botId}`, async (req, res) => {
|
|
||||||
|
|
||||||
return adapter.processActivity(req, res, async (context) => {
|
|
||||||
|
|
||||||
const state = conversationState.get(context);
|
|
||||||
const dc = min.dialogs.createContext(context, state);
|
|
||||||
|
|
||||||
const user = min.userState.get(dc.context);
|
|
||||||
if (!user.loaded) {
|
|
||||||
await min.conversationalService.sendEvent(
|
|
||||||
dc,
|
|
||||||
"loadInstance",
|
|
||||||
{
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
botId: instance.botId,
|
|
||||||
theme: instance.theme,
|
|
||||||
secret: instance.webchatKey, // TODO: Use token.
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
user.loaded = true;
|
|
||||||
user.subjects = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId},
|
|
||||||
ConversationID: ${context.activity.conversation.id},
|
|
||||||
Name: ${context.activity.name}, Text: ${context.activity.text}.`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (context.activity.type === "conversationUpdate" &&
|
|
||||||
context.activity.membersAdded.length > 0) {
|
|
||||||
|
|
||||||
let member = context.activity.membersAdded[0];
|
|
||||||
if (member.name === "GeneralBots") {
|
|
||||||
logger.info(`Bot added to conversation, starting chat...`);
|
|
||||||
appPackages.forEach(e => {
|
|
||||||
e.onNewSession(min, dc);
|
|
||||||
});
|
|
||||||
await dc.begin('/');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.info(`Member added to conversation: ${member.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (context.activity.type === 'message') {
|
|
||||||
|
|
||||||
// Check to see if anyone replied. If not then start echo dialog
|
|
||||||
|
|
||||||
if (context.activity.text === "admin") {
|
|
||||||
await dc.begin("/admin");
|
|
||||||
} else {
|
|
||||||
await dc.continue();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (context.activity.type === 'event') {
|
|
||||||
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") {
|
|
||||||
dc.begin("/answer", {
|
|
||||||
// TODO: query: context.activity.data,
|
|
||||||
fromFaq: true
|
|
||||||
});
|
|
||||||
} else if (context.activity.name === "quality") {
|
|
||||||
await dc.begin("/quality", {
|
|
||||||
// TODO: score: context.activity.data
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await dc.continue();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.` );
|
||||||
|
|
||||||
// Serves individual URL for each bot user interface.
|
// Serves individual URL for each bot user interface.
|
||||||
|
|
||||||
let uiUrl = `/${instance.botId}`;
|
let uiUrl = `/${instance.botId}`;
|
||||||
server.use(
|
server.use(
|
||||||
uiUrl,
|
uiUrl,
|
||||||
express.static(UrlJoin(this.deployFolder, uiPackage, "build"))
|
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build"))
|
||||||
);
|
);
|
||||||
logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`);
|
logger.info(`Bot UI ${uiPackage} acessible at: ${uiUrl}.`);
|
||||||
|
|
||||||
|
@ -338,166 +186,183 @@ export class GBMinService {
|
||||||
// );
|
// );
|
||||||
// next();
|
// next();
|
||||||
|
|
||||||
// Specialized load for each min instance.
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Performs package deployment in all .gbai or default. */
|
private async buildBotAdapter(instance: any) {
|
||||||
public deployPackages(core: IGBCoreService, server: any, appPackages: Array<IGBPackage>) {
|
|
||||||
let _this = this;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
let totalPackages = 0;
|
|
||||||
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH");
|
|
||||||
let paths = [this.deployFolder];
|
|
||||||
if (additionalPath) {
|
|
||||||
paths = paths.concat(additionalPath.toLowerCase().split(";"));
|
|
||||||
}
|
|
||||||
let botPackages = new Array<string>();
|
|
||||||
let gbappPackages = new Array<string>();
|
|
||||||
let generalPackages = new Array<string>();
|
|
||||||
|
|
||||||
function doIt(path) {
|
let adapter = new BotFrameworkAdapter({
|
||||||
const isDirectory = source => Fs.lstatSync(source).isDirectory()
|
appId: instance.marketplaceId,
|
||||||
const getDirectories = source =>
|
appPassword: instance.marketplacePassword
|
||||||
Fs.readdirSync(source).map(name => Path.join(source, name)).filter(isDirectory)
|
});
|
||||||
|
|
||||||
let dirs = getDirectories(path);
|
const storage = new MemoryStorage();
|
||||||
dirs.forEach(element => {
|
const conversationState = new ConversationState(storage);
|
||||||
if (element.startsWith('.')) {
|
const userState = new UserState(storage);
|
||||||
logger.info(`Ignoring ${element}...`);
|
adapter.use(new BotStateSet(conversationState, userState));
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (element.endsWith('.gbot')) {
|
|
||||||
botPackages.push(element);
|
|
||||||
}
|
|
||||||
else if (element.endsWith('.gbapp')) {
|
|
||||||
gbappPackages.push(element);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
generalPackages.push(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
// The minimal bot is built here.
|
||||||
|
|
||||||
|
let min = new GBMinInstance();
|
||||||
|
min.botId = instance.botId;
|
||||||
|
min.bot = adapter;
|
||||||
|
min.userState = userState;
|
||||||
|
min.core = this.core;
|
||||||
|
min.conversationalService = this.conversationalService;
|
||||||
|
min.instance = await this.core.loadInstance(min.botId);
|
||||||
|
min.dialogs.add('textPrompt', new TextPrompt());
|
||||||
|
|
||||||
|
return { min, adapter, conversationState };
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`);
|
private invokeLoadBot(appPackages: any[], min: any, server: any) {
|
||||||
paths.forEach(e => {
|
appPackages.forEach(e => {
|
||||||
logger.info(`Looking in: ${e}...`);
|
e.sysPackages = new Array<IGBPackage>();
|
||||||
doIt(e);
|
[GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage,
|
||||||
});
|
GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => {
|
||||||
|
logger.info(`Loading sys package: ${sysPackage.name}...`);
|
||||||
/** Deploys all .gbapp files first. */
|
let p = Object.create(sysPackage.prototype) as IGBPackage;
|
||||||
|
p.loadBot(min);
|
||||||
let appPackagesProcessed = 0;
|
e.sysPackages.push(p);
|
||||||
|
if (sysPackage.name === "GBWhatsappPackage") {
|
||||||
gbappPackages.forEach(e => {
|
let url = "/instances/:botId/whatsapp";
|
||||||
logger.info(`Deploying app: ${e}...`);
|
server.post(url, (req, res) => {
|
||||||
|
p["channel"].received(req, res);
|
||||||
// Skips .gbapp inside deploy folder.
|
|
||||||
if (!e.startsWith('deploy')) {
|
|
||||||
import(e).then(m => {
|
|
||||||
let p = new m.Package();
|
|
||||||
p.loadPackage(core, core.sequelize);
|
|
||||||
appPackages.push(p);
|
|
||||||
logger.info(`App (.gbapp) deployed: ${e}.`);
|
|
||||||
appPackagesProcessed++;
|
|
||||||
}).catch(err => {
|
|
||||||
logger.info(`Error deploying App (.gbapp): ${e}: ${err}`);
|
|
||||||
appPackagesProcessed++;
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
appPackagesProcessed++;
|
|
||||||
}
|
}
|
||||||
}, _this);
|
}, this);
|
||||||
|
e.loadBot(min);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private receiver(adapter: BotFrameworkAdapter, req: any, res: any, conversationState: ConversationState, min: any, instance: any, appPackages: any[]) {
|
||||||
WaitUntil()
|
return adapter.processActivity(req, res, async (context) => {
|
||||||
.interval(1000)
|
const state = conversationState.get(context);
|
||||||
.times(10)
|
const dc = min.dialogs.createContext(context, state);
|
||||||
.condition(function (cb) {
|
const user = min.userState.get(dc.context);
|
||||||
logger.info(`Waiting for app package deployment...`);
|
if (!user.loaded) {
|
||||||
cb(appPackagesProcessed == gbappPackages.length);
|
await min.conversationalService.sendEvent(dc, "loadInstance", {
|
||||||
})
|
instanceId: instance.instanceId,
|
||||||
.done(function (result) {
|
botId: instance.botId,
|
||||||
logger.info(`App Package deployment done.`);
|
theme: instance.theme,
|
||||||
|
secret: instance.webchatKey,
|
||||||
core.syncDatabaseStructure();
|
});
|
||||||
|
user.loaded = true;
|
||||||
/** Deploys all .gbot files first. */
|
user.subjects = [];
|
||||||
|
}
|
||||||
botPackages.forEach(e => {
|
logger.info(`[RCV]: ${context.activity.type}, ChannelID: ${context.activity.channelId},
|
||||||
logger.info(`Deploying bot: ${e}...`);
|
ConversationID: ${context.activity.conversation.id},
|
||||||
_this.deployer.deployBot(e);
|
Name: ${context.activity.name}, Text: ${context.activity.text}.`);
|
||||||
logger.info(`Bot: ${e} deployed...`);
|
if (context.activity.type === "conversationUpdate" &&
|
||||||
}, _this);
|
context.activity.membersAdded.length > 0) {
|
||||||
|
let member = context.activity.membersAdded[0];
|
||||||
/** Then all remaining generalPackages are loaded. */
|
if (member.name === "GeneralBots") {
|
||||||
|
logger.info(`Bot added to conversation, starting chat...`);
|
||||||
generalPackages.forEach(filename => {
|
appPackages.forEach(e => {
|
||||||
|
e.onNewSession(min, dc);
|
||||||
let filenameOnly = Path.basename(filename);
|
|
||||||
logger.info(`Deploying package: ${filename}...`);
|
|
||||||
|
|
||||||
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */
|
|
||||||
|
|
||||||
if (Path.extname(filename) === ".gbapp" || Path.extname(filename) === ".gblib") {
|
|
||||||
|
|
||||||
|
|
||||||
/** Themes for bots. */
|
|
||||||
|
|
||||||
} else if (Path.extname(filename) === ".gbtheme") {
|
|
||||||
server.use("/themes/" + filenameOnly, express.static(filename));
|
|
||||||
logger.info(`Theme (.gbtheme) assets accessible at: ${"/themes/" + filenameOnly}.`);
|
|
||||||
|
|
||||||
|
|
||||||
/** Knowledge base for bots. */
|
|
||||||
|
|
||||||
} else if (Path.extname(filename) === ".gbkb") {
|
|
||||||
server.use(
|
|
||||||
"/kb/" + filenameOnly + "/subjects",
|
|
||||||
express.static(UrlJoin(filename, "subjects"))
|
|
||||||
);
|
|
||||||
logger.info(`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (Path.extname(filename) === ".gbui" || filename.endsWith(".git")) {
|
|
||||||
// Already Handled
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unknown package format. */
|
|
||||||
|
|
||||||
else {
|
|
||||||
let err = new Error(`Package type not handled: ${filename}.`);
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
totalPackages++;
|
|
||||||
});
|
|
||||||
|
|
||||||
WaitUntil()
|
|
||||||
.interval(1000)
|
|
||||||
.times(5)
|
|
||||||
.condition(function (cb) {
|
|
||||||
logger.info(`Waiting for package deployment...`);
|
|
||||||
cb(totalPackages == (generalPackages.length));
|
|
||||||
})
|
|
||||||
.done(function (result) {
|
|
||||||
if (botPackages.length === 0) {
|
|
||||||
logger.info(`The bot server is running empty: No bot instances have been found, at least one .gbot file must be deployed.`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.info(`Package deployment done.`);
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
await dc.begin('/');
|
||||||
} catch (err) {
|
}
|
||||||
logger.error(err);
|
else {
|
||||||
reject(err)
|
logger.info(`Member added to conversation: ${member.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (context.activity.type === 'message') {
|
||||||
|
// Check to see if anyone replied. If not then start echo dialog
|
||||||
|
if (context.activity.text === "admin") {
|
||||||
|
await dc.begin("/admin");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await dc.continue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (context.activity.type === 'event') {
|
||||||
|
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") {
|
||||||
|
dc.begin("/answer", {
|
||||||
|
// TODO: query: context.activity.data,
|
||||||
|
fromFaq: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (context.activity.name === "quality") {
|
||||||
|
await dc.begin("/quality", {
|
||||||
|
// TODO: score: context.activity.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await dc.continue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Webchat key from Bot Service.
|
||||||
|
*
|
||||||
|
* @param instance The Bot instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async getWebchatToken(instance: any) {
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
url:
|
||||||
|
"https://directline.botframework.com/v3/directline/tokens/generate",
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${instance.webchatKey}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let json = await request(options);
|
||||||
|
return Promise.resolve(JSON.parse(json));
|
||||||
|
} catch (error) {
|
||||||
|
let msg = `Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
|
||||||
|
logger.error(msg);
|
||||||
|
return Promise.reject(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Speech to Text / Text to Speech token from the provider.
|
||||||
|
*
|
||||||
|
* @param instance The general bot instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async getSTSToken(instance: any) {
|
||||||
|
|
||||||
|
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
url:
|
||||||
|
"https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Ocp-Apim-Subscription-Key": instance.speechKey
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await request(options);
|
||||||
|
} catch (error) {
|
||||||
|
let msg = `Error calling Speech to Text client. Error is: ${error}.`;
|
||||||
|
logger.error(msg);
|
||||||
|
return Promise.reject(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -55,13 +55,13 @@ export class FaqDialog extends IGBDialog {
|
||||||
await min.conversationalService.sendEvent(dc, "play", {
|
await min.conversationalService.sendEvent(dc, "play", {
|
||||||
playerType: "bullet",
|
playerType: "bullet",
|
||||||
data: data.slice(0, 10)
|
data: data.slice(0, 10)
|
||||||
});
|
})
|
||||||
|
|
||||||
let messages = [
|
let messages = [
|
||||||
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.",
|
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.",
|
||||||
"Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.",
|
"Você pode clicar em alguma destas perguntas da tela que eu te respondo de imediato.",
|
||||||
"Veja a lista que eu preparei logo aí na tela..."
|
"Veja a lista que eu preparei logo aí na tela..."
|
||||||
];
|
]
|
||||||
|
|
||||||
await dc.context.sendActivity(messages[0]); // TODO: RND messages.
|
await dc.context.sendActivity(messages[0]); // TODO: RND messages.
|
||||||
await dc.endAll();
|
await dc.endAll();
|
||||||
|
|
|
@ -435,7 +435,7 @@ export class KBService {
|
||||||
answerId: answer1.answerId,
|
answerId: answer1.answerId,
|
||||||
packageId: packageId
|
packageId: packageId
|
||||||
});
|
});
|
||||||
logger.info(`Question created: ${question.questionId}`)
|
|
||||||
return Promise.resolve(question)
|
return Promise.resolve(question)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -107,7 +107,7 @@ export class GBServer {
|
||||||
p.loadPackage(core, core.sequelize);
|
p.loadPackage(core, core.sequelize);
|
||||||
});
|
});
|
||||||
|
|
||||||
await minService.deployPackages(core, server, appPackages);
|
await deployer.deployPackages(core, server, appPackages);
|
||||||
logger.info(`The Bot Server is in RUNNING mode...`);
|
logger.info(`The Bot Server is in RUNNING mode...`);
|
||||||
|
|
||||||
let instance = await minService.buildMin(server, appPackages);
|
let instance = await minService.buildMin(server, appPackages);
|
||||||
|
|
Loading…
Add table
Reference in a new issue