feat(whatsapp.gblib): Now Whatsapp will display markdown from .gbkb including images.
This commit is contained in:
parent
246b2226bf
commit
faa5ec710c
8 changed files with 218 additions and 96 deletions
|
@ -63,7 +63,6 @@ export class SwitchBotDialog extends IGBDialog {
|
|||
async step => {
|
||||
let sec = new SecService();
|
||||
let from = step.context.activity.from.id;
|
||||
const minBoot = GBServer.globals.minInstances[0];
|
||||
await sec.updateCurrentBotId(from, step.result);
|
||||
await step.context.sendActivity(`Opa, vamos lá!`);
|
||||
|
||||
|
|
|
@ -64,11 +64,17 @@ export class GBConversationalService implements IGBConversationalService {
|
|||
}
|
||||
|
||||
public async sendFile(min: GBMinInstance, step: GBDialogStep, url: string): Promise<any> {
|
||||
let mobile = step.context.activity.from.id;
|
||||
min.whatsAppDirectLine.sendFile(mobile, url);
|
||||
const mobile = step.context.activity.from.id;
|
||||
const filename = url.substring(url.lastIndexOf('/')+1);
|
||||
await min.whatsAppDirectLine.sendFileToDevice(mobile, url, filename);
|
||||
|
||||
}
|
||||
|
||||
public async sendAudio(min: GBMinInstance, step: GBDialogStep, url: string): Promise<any> {
|
||||
const mobile = step.context.activity.from.id;
|
||||
await min.whatsAppDirectLine.sendAudioToDevice(mobile, url);
|
||||
}
|
||||
|
||||
public async sendEvent(step: GBDialogStep, name: string, value: Object): Promise<any> {
|
||||
if (step.context.activity.channelId === 'webchat') {
|
||||
const msg = MessageFactory.text('');
|
||||
|
|
|
@ -203,6 +203,10 @@ export class GBDeployer {
|
|||
instance.whatsappServiceNumber = bootInstance.whatsappServiceNumber;
|
||||
instance.whatsappServiceUrl = bootInstance.whatsappServiceUrl;
|
||||
instance.whatsappServiceWebhookUrl = bootInstance.whatsappServiceWebhookUrl;
|
||||
instance.storageServer = bootInstance.storageServer;
|
||||
instance.storageName = bootInstance.storageName;
|
||||
instance.storageUsername = bootInstance.storageUsername;
|
||||
instance.storagePassword = bootInstance.storagePassword;
|
||||
|
||||
instance = await service.internalDeployBot(
|
||||
instance,
|
||||
|
@ -422,9 +426,7 @@ export class GBDeployer {
|
|||
server.use(`/themes/${filenameOnly}`, express.static(filename));
|
||||
GBLog.info(`Theme (.gbtheme) assets accessible at: /themes/${filenameOnly}.`);
|
||||
} else if (Path.extname(filename) === '.gbkb') {
|
||||
server.use(`/kb/${filenameOnly}/subjects`, express.static(urlJoin(filename, 'subjects')));
|
||||
server.use(`/kb/${filenameOnly}/images`, express.static(urlJoin(filename, 'images')));
|
||||
GBLog.info(`KB (.gbkb) assets accessible at: /kb/${filenameOnly}.`);
|
||||
this.mountGBKBAssets( filenameOnly, filename);
|
||||
} else if (Path.extname(filename) === '.gbui') {
|
||||
// Already Handled
|
||||
} else if (Path.extname(filename) === '.gbdialog') {
|
||||
|
@ -458,6 +460,13 @@ export class GBDeployer {
|
|||
return { generalPackages, totalPackages };
|
||||
}
|
||||
|
||||
private mountGBKBAssets(packageName: any, filename: string) {
|
||||
GBServer.globals.server.use(`/kb/${packageName}/subjects`, express.static(urlJoin(filename, 'subjects')));
|
||||
GBServer.globals.server.use(`/kb/${packageName}/images`, express.static(urlJoin(filename, 'images')));
|
||||
GBServer.globals.server.use(`/kb/${packageName}/audios`, express.static(urlJoin(filename, 'audios')));
|
||||
GBLog.info(`KB (.gbkb) assets accessible at: /kb/${packageName}.`);
|
||||
}
|
||||
|
||||
private isSystemPackage(name: string): Boolean {
|
||||
const names = ['core.gbapp', 'admin.gbapp', 'azuredeployer.gbapp', 'customer-satisfaction.gbapp', 'kb.gbapp'];
|
||||
|
||||
|
|
|
@ -138,9 +138,8 @@ export class GBMinService {
|
|||
return; // Exit here.
|
||||
}
|
||||
|
||||
const minBoot = GBServer.globals.bootInstance;
|
||||
const toSwitchMin = GBServer.globals.minInstances.filter(p => p.botId === text)[0];
|
||||
let activeMin = toSwitchMin ? toSwitchMin : minBoot;
|
||||
let activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot;
|
||||
|
||||
let sec = new SecService();
|
||||
let user = await sec.getUserFromPhone(id);
|
||||
|
@ -190,6 +189,10 @@ export class GBMinService {
|
|||
|
||||
// Build bot adapter.
|
||||
const { min, adapter, conversationState } = await this.buildBotAdapter(instance, GBServer.globals.publicAddress, GBServer.globals.sysPackages);
|
||||
|
||||
if (GBServer.globals.minInstances.length === 0) {
|
||||
GBServer.globals.minBoot = min;
|
||||
}
|
||||
GBServer.globals.minInstances.push(min);
|
||||
|
||||
// Install default VBA module.
|
||||
|
@ -453,11 +456,13 @@ export class GBMinService {
|
|||
user.cb = undefined;
|
||||
await min.userProfile.set(step.context, user);
|
||||
|
||||
let sec = new SecService();
|
||||
const member = context.activity.membersAdded[0];
|
||||
if (context.activity.membersAdded !== undefined) {
|
||||
let sec = new SecService();
|
||||
const member = context.activity.membersAdded[0];
|
||||
|
||||
await sec.ensureUser(instance.instanceId, member.id,
|
||||
min.botId, member.id, "", "web", member.name, member.id);
|
||||
await sec.ensureUser(instance.instanceId, member.id,
|
||||
min.botId, member.id, "", "web", member.name, member.id);
|
||||
}
|
||||
}
|
||||
|
||||
GBLog.info(
|
||||
|
|
|
@ -144,7 +144,7 @@ export class AskDialog extends IGBDialog {
|
|||
|
||||
// Sends the answer to all outputs, including projector.
|
||||
|
||||
await service.sendAnswer(AskDialog.getChannel(step), min.conversationalService, step, resultsA.answer);
|
||||
await service.sendAnswer(min, AskDialog.getChannel(step), step, resultsA.answer);
|
||||
|
||||
// Goes to ask loop, again.
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
|
@ -164,7 +164,7 @@ export class AskDialog extends IGBDialog {
|
|||
await step.context.sendActivity(Messages[locale].wider_answer);
|
||||
}
|
||||
// Sends the answer to all outputs, including projector.
|
||||
await service.sendAnswer(AskDialog.getChannel(step), min.conversationalService, step, resultsB.answer);
|
||||
await service.sendAnswer(min, AskDialog.getChannel(step), step, resultsB.answer);
|
||||
|
||||
return await step.replaceDialog('/ask', { isReturning: true });
|
||||
} else {
|
||||
|
@ -180,7 +180,7 @@ export class AskDialog extends IGBDialog {
|
|||
}
|
||||
|
||||
private static getChannel(step): string {
|
||||
return Number.isInteger(step.context.activity.from.id) ? 'whatsapp' : step.context.activity.channelId;
|
||||
return !isNaN(step.context.activity.from.id) ? 'whatsapp' : step.context.activity.channelId;
|
||||
}
|
||||
|
||||
private static getAnswerEventDialog(service: KBService, min: GBMinInstance) {
|
||||
|
@ -191,7 +191,7 @@ export class AskDialog extends IGBDialog {
|
|||
const question = await service.getQuestionById(min.instance.instanceId, data.questionId);
|
||||
const answer = await service.getAnswerById(min.instance.instanceId, question.answerId);
|
||||
// Sends the answer to all outputs, including projector.
|
||||
await service.sendAnswer(AskDialog.getChannel(step), min.conversationalService, step, answer);
|
||||
await service.sendAnswer(min, AskDialog.getChannel(step), step, answer);
|
||||
await step.replaceDialog('/ask', { isReturning: true });
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ const walkPromise = require('walk-promise');
|
|||
const parse = require('bluebird').promisify(require('csv-parse'));
|
||||
const { SearchService } = require('azure-search-client');
|
||||
|
||||
import { GBDialogStep, GBLog, IGBConversationalService, IGBCoreService, IGBInstance } from 'botlib';
|
||||
import { GBDialogStep, GBLog, IGBConversationalService, IGBCoreService, IGBInstance, GBMinInstance } from 'botlib';
|
||||
import { Sequelize } from 'sequelize-typescript';
|
||||
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
|
||||
import { GuaribasPackage } from '../../core.gbapp/models/GBModel';
|
||||
|
@ -54,6 +54,8 @@ import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
|
|||
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models';
|
||||
import { Messages } from '../strings';
|
||||
import { GBConfigService } from './../../core.gbapp/services/GBConfigService';
|
||||
import { GBServer } from '../../../src/app';
|
||||
|
||||
|
||||
/**
|
||||
* Result for quey on KB data.
|
||||
|
@ -349,21 +351,28 @@ export class KBService {
|
|||
});
|
||||
}
|
||||
|
||||
public async sendAnswer(channel: string, conversationalService: IGBConversationalService, step: GBDialogStep, answer: GuaribasAnswer) {
|
||||
public async sendAnswer(min: GBMinInstance, channel: string, step: GBDialogStep, answer: GuaribasAnswer) {
|
||||
if (answer.content.endsWith('.mp4')) {
|
||||
await this.playVideo(conversationalService, step, answer);
|
||||
await this.playVideo(min.conversationalService, step, answer);
|
||||
}
|
||||
else if (answer.format === '.md') {
|
||||
|
||||
await this.playMarkdown(answer, channel, step, conversationalService);
|
||||
await this.playMarkdown(min, answer, channel, step, min.conversationalService);
|
||||
|
||||
} else if (answer.content.endsWith('.ogg')) {
|
||||
|
||||
await this.playAudio(min, answer, channel, step, min.conversationalService);
|
||||
} else {
|
||||
await step.context.sendActivity(answer.content);
|
||||
await conversationalService.sendEvent(step, 'stop', undefined);
|
||||
await min.conversationalService.sendEvent(step, 'stop', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private async playMarkdown(answer: GuaribasAnswer, channel: string, step: GBDialogStep, conversationalService: IGBConversationalService) {
|
||||
private async playAudio(min: GBMinInstance, answer: GuaribasAnswer, channel: string, step: GBDialogStep, conversationalService: IGBConversationalService) {
|
||||
conversationalService.sendAudio(min, step, answer.content);
|
||||
}
|
||||
|
||||
private async playMarkdown(min: GBMinInstance, answer: GuaribasAnswer, channel: string, step: GBDialogStep, conversationalService: IGBConversationalService) {
|
||||
let html = answer.content;
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
|
@ -392,12 +401,82 @@ export class KBService {
|
|||
});
|
||||
}
|
||||
else if (channel === 'whatsapp') {
|
||||
let from = step.context.activity.from.id;
|
||||
//conversationalService.sendFile(min, from, answer.content);
|
||||
await this.sendMarkdownToMobile(step, answer, conversationalService, min);
|
||||
}
|
||||
}
|
||||
|
||||
private async playVideo(conversationalService: IGBConversationalService, step: GBDialogStep, answer: GuaribasAnswer) {
|
||||
private async sendMarkdownToMobile(step: GBDialogStep, answer: GuaribasAnswer, conversationalService: IGBConversationalService, min: GBMinInstance) {
|
||||
|
||||
let text = answer.content;
|
||||
|
||||
enum State {
|
||||
InText,
|
||||
InImage,
|
||||
InImageBegin,
|
||||
InImageCaption,
|
||||
InImageAddressBegin,
|
||||
InImageAddressBody
|
||||
};
|
||||
let state = State.InText;
|
||||
let currentImage = '';
|
||||
let currentText = '';
|
||||
|
||||
//
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
const c = text.charAt(i);
|
||||
|
||||
switch (state) {
|
||||
case State.InText:
|
||||
if (c === '!') {
|
||||
state = State.InImageBegin;
|
||||
}
|
||||
else {
|
||||
currentText = currentText.concat(c);
|
||||
}
|
||||
break;
|
||||
case State.InImageBegin:
|
||||
if (c === '[') {
|
||||
if (currentText !== '') {
|
||||
await step.context.sendActivity(currentText);
|
||||
}
|
||||
|
||||
currentText = '';
|
||||
state = State.InImageCaption;
|
||||
}
|
||||
else {
|
||||
state = State.InText;
|
||||
currentText = currentText.concat('!').concat(c);
|
||||
}
|
||||
break;
|
||||
case State.InImageCaption:
|
||||
if (c === ']') {
|
||||
state = State.InImageAddressBegin;
|
||||
}
|
||||
break;
|
||||
case State.InImageAddressBegin:
|
||||
if (c === '(') {
|
||||
state = State.InImageAddressBody;
|
||||
}
|
||||
break;
|
||||
case State.InImageAddressBody:
|
||||
if (c === ')') {
|
||||
state = State.InText;
|
||||
let url = urlJoin(GBServer.globals.publicAddress, currentImage);
|
||||
await conversationalService.sendFile(min, step, url);
|
||||
}
|
||||
else {
|
||||
currentImage = currentImage.concat(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (currentText !== '') {
|
||||
await step.context.sendActivity(currentText);
|
||||
}
|
||||
}
|
||||
|
||||
private async playVideo(conversationalService: IGBConversationalService, step: GBDialogStep, answer: GuaribasAnswer) {
|
||||
await conversationalService.sendEvent(step, 'play', {
|
||||
playerType: 'video',
|
||||
data: answer.content
|
||||
|
@ -405,74 +484,74 @@ private async playVideo(conversationalService: IGBConversationalService, step: G
|
|||
}
|
||||
|
||||
public async importKbPackage(
|
||||
localPath: string,
|
||||
packageStorage: GuaribasPackage,
|
||||
instance: IGBInstance
|
||||
): Promise < any > {
|
||||
// Imports subjects tree into database and return it.
|
||||
localPath: string,
|
||||
packageStorage: GuaribasPackage,
|
||||
instance: IGBInstance
|
||||
): Promise<any> {
|
||||
// Imports subjects tree into database and return it.
|
||||
|
||||
await this.importSubjectFile(packageStorage.packageId, urlJoin(localPath, 'subjects.json'), instance);
|
||||
await this.importSubjectFile(packageStorage.packageId, urlJoin(localPath, 'subjects.json'), instance);
|
||||
|
||||
// Import all .tsv files in the tabular directory.
|
||||
// Import all .tsv files in the tabular directory.
|
||||
|
||||
return this.importKbTabularDirectory(localPath, instance, packageStorage.packageId);
|
||||
}
|
||||
return this.importKbTabularDirectory(localPath, instance, packageStorage.packageId);
|
||||
}
|
||||
|
||||
public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise < any > {
|
||||
const files = await walkPromise(urlJoin(localPath, 'tabular'));
|
||||
public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
|
||||
const files = await walkPromise(urlJoin(localPath, 'tabular'));
|
||||
|
||||
return Promise.all(
|
||||
files.map(async file => {
|
||||
if (file.name.endsWith('.xlsx')) {
|
||||
return this.importKbTabularFile(urlJoin(file.root, file.name), instance.instanceId, packageId);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(
|
||||
files.map(async file => {
|
||||
if (file.name.endsWith('.xlsx')) {
|
||||
return this.importKbTabularFile(urlJoin(file.root, file.name), instance.instanceId, packageId);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async importSubjectFile(packageId: number, filename: string, instance: IGBInstance): Promise < any > {
|
||||
const subjectsLoaded = JSON.parse(Fs.readFileSync(filename, 'utf8'));
|
||||
public async importSubjectFile(packageId: number, filename: string, instance: IGBInstance): Promise<any> {
|
||||
const subjectsLoaded = JSON.parse(Fs.readFileSync(filename, 'utf8'));
|
||||
|
||||
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
|
||||
return asyncPromise.eachSeries(subjects, async item => {
|
||||
const value = await GuaribasSubject.create({
|
||||
internalId: item.id,
|
||||
parentSubjectId: parentSubjectId,
|
||||
instanceId: instance.instanceId,
|
||||
from: item.from,
|
||||
to: item.to,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
packageId: packageId
|
||||
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
|
||||
return asyncPromise.eachSeries(subjects, async item => {
|
||||
const value = await GuaribasSubject.create({
|
||||
internalId: item.id,
|
||||
parentSubjectId: parentSubjectId,
|
||||
instanceId: instance.instanceId,
|
||||
from: item.from,
|
||||
to: item.to,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
packageId: packageId
|
||||
});
|
||||
|
||||
if (item.children) {
|
||||
return Promise.resolve(doIt(item.children, value.subjectId));
|
||||
} else {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (item.children) {
|
||||
return Promise.resolve(doIt(item.children, value.subjectId));
|
||||
} else {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return doIt(subjectsLoaded.children, undefined);
|
||||
}
|
||||
return doIt(subjectsLoaded.children, undefined);
|
||||
}
|
||||
|
||||
public async undeployKbFromStorage(instance: IGBInstance, deployer: GBDeployer, packageId: number) {
|
||||
await GuaribasQuestion.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasAnswer.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasSubject.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasPackage.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasQuestion.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasAnswer.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasSubject.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
await GuaribasPackage.destroy({
|
||||
where: { instanceId: instance.instanceId, packageId: packageId }
|
||||
});
|
||||
|
||||
await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
|
||||
}
|
||||
await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploys a knowledge base to the storage using the .gbkb format.
|
||||
|
@ -480,17 +559,17 @@ private async playVideo(conversationalService: IGBConversationalService, step: G
|
|||
* @param localPath Path to the .gbkb folder.
|
||||
*/
|
||||
public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) {
|
||||
const packageType = Path.extname(localPath);
|
||||
const packageName = Path.basename(localPath);
|
||||
GBLog.info(`[GBDeployer] Opening package: ${localPath}`);
|
||||
const packageObject = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
|
||||
const packageType = Path.extname(localPath);
|
||||
const packageName = Path.basename(localPath);
|
||||
GBLog.info(`[GBDeployer] Opening package: ${localPath}`);
|
||||
const packageObject = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
|
||||
|
||||
const instance = await core.loadInstance(packageObject.botId);
|
||||
GBLog.info(`[GBDeployer] Importing: ${localPath}`);
|
||||
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
|
||||
await this.importKbPackage(localPath, p, instance);
|
||||
const instance = await core.loadInstance(packageObject.botId);
|
||||
GBLog.info(`[GBDeployer] Importing: ${localPath}`);
|
||||
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
|
||||
await this.importKbPackage(localPath, p, instance);
|
||||
|
||||
deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
|
||||
GBLog.info(`[GBDeployer] Finished import of ${localPath}`);
|
||||
}
|
||||
deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
|
||||
GBLog.info(`[GBDeployer] Finished import of ${localPath}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,14 +231,15 @@ export class WhatsappDirectLine extends GBService {
|
|||
return `${attachment.content.title} - ${attachment.content.text}`;
|
||||
}
|
||||
|
||||
public async sendFileToDevice(to, url) {
|
||||
public async sendFileToDevice(to, url, filename) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: urlJoin(this.whatsappServiceUrl, 'sendFile'),
|
||||
qs: {
|
||||
token: this.whatsappServiceKey,
|
||||
phone: to,
|
||||
body: url
|
||||
body: url,
|
||||
filename: filename
|
||||
},
|
||||
headers: {
|
||||
'cache-control': 'no-cache'
|
||||
|
@ -254,6 +255,28 @@ export class WhatsappDirectLine extends GBService {
|
|||
}
|
||||
}
|
||||
|
||||
public async sendAudioToDevice(to, url) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: urlJoin(this.whatsappServiceUrl, 'sendPTT'),
|
||||
qs: {
|
||||
token: this.whatsappServiceKey,
|
||||
phone: to,
|
||||
audio:url
|
||||
},
|
||||
headers: {
|
||||
'cache-control': 'no-cache'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// tslint:disable-next-line: await-promise
|
||||
const result = await request.post(options);
|
||||
GBLog.info(result);
|
||||
} catch (error) {
|
||||
GBLog.error(`Error sending message to Whatsapp provider ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async sendToDevice(to, msg) {
|
||||
const options = {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
import { GBLog, IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
|
||||
import { GBLog, IGBCoreService, IGBInstance, IGBPackage, GBMinInstance } from 'botlib';
|
||||
import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService';
|
||||
import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService';
|
||||
import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService';
|
||||
|
@ -60,6 +60,7 @@ export class RootData {
|
|||
minService: GBMinService;
|
||||
bootInstance: IGBInstance;
|
||||
public minInstances: any[];
|
||||
minBoot: GBMinInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue