* NEW: kb.gbapp now has a complete browser of excel articles.

* FIX: Some security improved.
* NEW: Protocol changes for exchanging questions between UI and Bot Server.
This commit is contained in:
Rodrigo Rodriguez 2018-09-20 12:35:47 -03:00
parent 708a27e419
commit 379ade60fb
10 changed files with 170 additions and 101 deletions

View file

@ -107,7 +107,7 @@ export class AdminDialog extends IGBDialog {
let importer = new GBImporter(min.core); let importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer); let deployer = new GBDeployer(min.core, importer);
min.dialogs.add("/admin", [ min.dialogs.add("/adminRat", [
async dc => { async dc => {
await AdminDialog.refreshAdminToken(min, dc); await AdminDialog.refreshAdminToken(min, dc);
// await dc.context.sendActivity( // await dc.context.sendActivity(
@ -137,7 +137,7 @@ export class AdminDialog extends IGBDialog {
} }
]); ]);
min.dialogs.add("/admin1", [ min.dialogs.add("/admin", [
async (dc, args) => { async (dc, args) => {
const prompt = "Please, authenticate:"; const prompt = "Please, authenticate:";
await dc.prompt("textPrompt", prompt); await dc.prompt("textPrompt", prompt);

View file

@ -291,9 +291,9 @@ export class GBMinService {
} }
logger.info( logger.info(
`[RCV]: ${context.activity.type}, ChannelID: ${ `[User]: ${context.activity.type}, ChannelID: ${
context.activity.channelId context.activity.channelId
}, Name: ${context.activity.name}, Text: ${context.activity.text}.` } Text: ${context.activity.text}.`
) )
if ( if (
context.activity.type === "conversationUpdate" && context.activity.type === "conversationUpdate" &&
@ -358,9 +358,9 @@ export class GBMinService {
}) })
} else if (context.activity.name === "showFAQ") { } else if (context.activity.name === "showFAQ") {
await dc.begin("/faq") await dc.begin("/faq")
} else if (context.activity.name === "ask") { } else if (context.activity.name === "answerEvent") {
await dc.begin("/answer", { await dc.begin("/answerEvent", {
query: (context.activity as any).data, questionId: (context.activity as any).data,
fromFaq: true fromFaq: true
}) })
@ -374,7 +374,7 @@ export class GBMinService {
} }
} }
} catch (error) { } catch (error) {
let msg = `Error in main activity: ${error.message}` let msg = `Error in main activity: ${error.message} ${error.stack? error.stack:""}`
logger.error(msg) logger.error(msg)
} }
}) })

View file

@ -57,9 +57,11 @@ class GBUIApp extends React.Component {
token: null, token: null,
instanceClient: null instanceClient: null
}; };
window.user = this.getUser()
} }
sendToken(token) { sendToken(token) {
setTimeout(() => { setTimeout(() => {
window.botConnection window.botConnection
.postActivity({ .postActivity({
@ -69,7 +71,7 @@ class GBUIApp extends React.Component {
locale: "en-us", locale: "en-us",
textFormat: "plain", textFormat: "plain",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" } from: this.getUser()
}) })
.subscribe(() => { .subscribe(() => {
window.userAgentApplication.logout(); window.userAgentApplication.logout();
@ -86,10 +88,11 @@ class GBUIApp extends React.Component {
locale: "en-us", locale: "en-us",
textFormat: "plain", textFormat: "plain",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" } from: this.getUser()
}) })
.subscribe(console.log("EVENT SENT TO Guaribas.")); .subscribe(console.log("EVENT SENT TO Guaribas."));
} }
getUser() { getUser() {
return { id: "webUser@gb", name: "You" }; return { id: "webUser@gb", name: "You" };
} }

View file

@ -41,7 +41,7 @@ class SideBarMenu extends React.Component {
locale: "en-us", locale: "en-us",
textFormat: "plain", textFormat: "plain",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" } from: window.user
}) })
.subscribe(console.log("success")); .subscribe(console.log("success"));
} }

View file

@ -39,12 +39,12 @@ class RenderItem extends Component {
window.botConnection window.botConnection
.postActivity({ .postActivity({
type: "event", type: "event",
name: "ask", name: "answerEvent",
data: item.content, data: item.questionId,
locale: "en-us", locale: "en-us",
textFormat: "plain", textFormat: "plain",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
from: { id: "webUser", name: "You" } from: window.user
}) })
.subscribe(console.log("success")); .subscribe(console.log("success"));
},400); },400);

View file

@ -33,6 +33,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
class GBMarkdownPlayer extends Component { class GBMarkdownPlayer extends Component {
send(value) { send(value) {
setTimeout(() => { setTimeout(() => {
window.botConnection window.botConnection
@ -49,15 +50,35 @@ class GBMarkdownPlayer extends Component {
}, 400); }, 400);
} }
sendAnswer(text) {
setTimeout(() => {
window.botConnection
.postActivity({
type: "event",
name: "answerEvent",
data: text,
locale: "en-us",
textFormat: "plain",
timestamp: new Date().toISOString(),
from: window.user
})
.subscribe(console.log("success"));
}, 400);
}
constructor() { constructor() {
super(); super();
this.state = { this.state = {
content: "" content: "",
prevId: 0,
nextId: 0
}; };
} }
play(data) { play(data) {
this.setState({ content: data }); this.setState({ content: data.content, prevId: data.prevId, nextId: data.nextId });
} }
stop() { stop() {
@ -78,7 +99,7 @@ class GBMarkdownPlayer extends Component {
render() { render() {
var quality = var quality =
<div className="gb-markdown-player-quality"> <div className="gb-markdown-player-quality">
<span ref={i => (this.quality = i)}>Is the answer OK?</span> <span ref={i => (this.quality = i)}>Is the answer OK?</span>
&nbsp;&nbsp; &nbsp;&nbsp;
@ -91,18 +112,34 @@ class GBMarkdownPlayer extends Component {
</button> </button>
</div>; </div>;
if (this.state.content === "") { var next = "", prev = "";
quality = "";
} if (this.state.content === "") {
quality = "";
}
if (this.state.prevId) {
prev = <a style={{ color: 'blue' }}
onPress={() => this.sendAnswer(this.state.prevId)}>
Back
</a>
}
if (this.state.nextId) {
next = <a style={{ color: 'blue' }}
onPress={() => this.sendAnswer(this.state.nextId)}>
Next
</a>
}
return ( return (
<div ref={i => (this.playerText = i)} className="media-player"> <div ref={i => (this.playerText = i)} className="media-player">
<div className="media-player-container"> <div className="media-player-container">
<div className="media-player-scroll"> <div className="media-player-scroll">
<div><span>{prev}</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>{next}</span></div>
<span dangerouslySetInnerHTML={this.createMarkup()} /> <span dangerouslySetInnerHTML={this.createMarkup()} />
</div> </div>
</div> </div>
{quality} {quality}
</div> </div>
); );
} }

View file

@ -39,6 +39,7 @@ import { KBService } from "./../services/KBService";
import { BotAdapter } from "botbuilder"; import { BotAdapter } from "botbuilder";
import { Messages } from "../strings"; import { Messages } from "../strings";
import { LuisRecognizer } from "botbuilder-ai"; import { LuisRecognizer } from "botbuilder-ai";
import { GuaribasQuestion } from "../models";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
@ -58,6 +59,24 @@ export class AskDialog extends IGBDialog {
serviceEndpoint: min.instance.nlpServerUrl serviceEndpoint: min.instance.nlpServerUrl
}); });
min.dialogs.add("/answerEvent", [
async (dc, args) => {
if (args && args.questionId) {
let question = await service.getQuestionById(min.instance.instanceId, args.questionId);
let answer = await service.getAnswerById(min.instance.instanceId, question.answerId)
// Sends the answer to all outputs, including projector.
await service.sendAnswer(
min.conversationalService,
dc,
answer
);
}
}])
min.dialogs.add("/answer", [ min.dialogs.add("/answer", [
async (dc, args) => { async (dc, args) => {
// Initialize values. // Initialize values.

View file

@ -84,7 +84,7 @@ export class MenuDialog extends IGBDialog {
) )
await min.conversationalService.sendEvent(dc, "play", { await min.conversationalService.sendEvent(dc, "play", {
playerType: "bullet", playerType: "bullet",
data: data.slice(0, 6) data: data.slice(0, 10)
}) })
} }
} else { } else {
@ -109,12 +109,13 @@ export class MenuDialog extends IGBDialog {
var subject = item var subject = item
var card = CardFactory.heroCard( var card = CardFactory.heroCard(
subject.title, subject.title,
subject.description,
CardFactory.images([ CardFactory.images([
UrlJoin( UrlJoin(
"/kb", "/kb",
min.instance.kb, min.instance.kb,
"subjects", "subjects",
subject.internalId + ".png" // TODO: or fallback to subject.png "subject.png"
) )
]), ]),
CardFactory.actions([ CardFactory.actions([
@ -123,7 +124,9 @@ export class MenuDialog extends IGBDialog {
title: Messages[locale].menu_select, title: Messages[locale].menu_select,
value: JSON.stringify({ value: JSON.stringify({
title: subject.title, title: subject.title,
description: subject.description,
subjectId: subject.subjectId, subjectId: subject.subjectId,
internalId: subject.internalId,
to: subject.to to: subject.to
}) })
} }

View file

@ -54,7 +54,8 @@ import {
DataType, DataType,
IsUUID, IsUUID,
PrimaryKey, PrimaryKey,
AutoIncrement AutoIncrement,
HasOne
} from "sequelize-typescript" } from "sequelize-typescript"
import { GuaribasUser } from "../../security.gblib/models" import { GuaribasUser } from "../../security.gblib/models"
@ -71,6 +72,7 @@ export class GuaribasSubject extends Model<GuaribasSubject> {
@Column title: string @Column title: string
@Column(DataType.STRING(512))
@Column description: string @Column description: string
@Column from: string @Column from: string
@ -201,6 +203,20 @@ export class GuaribasAnswer extends Model<GuaribasAnswer> {
@HasMany(() => GuaribasQuestion) @HasMany(() => GuaribasQuestion)
questions: GuaribasQuestion[] questions: GuaribasQuestion[]
@HasOne(() => GuaribasQuestion)
prev: GuaribasQuestion
@HasOne(() => GuaribasQuestion)
next: GuaribasQuestion
@ForeignKey(() => GuaribasQuestion)
@Column
nextId: number
@ForeignKey(() => GuaribasQuestion)
@Column
prevId: number
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number instanceId: number

View file

@ -63,23 +63,28 @@ export class KBService {
this.sequelize = sequelize this.sequelize = sequelize
} }
async getQuestionById(
instanceId: number,
questionId: number
): Promise<GuaribasQuestion> {
return GuaribasQuestion.findOne({
where: {
instanceId: instanceId,
questionId: questionId
}
})
}
async getAnswerById( async getAnswerById(
instanceId: number, instanceId: number,
answerId: number answerId: number
): Promise<GuaribasAnswer> { ): Promise<GuaribasAnswer> {
return new Promise<GuaribasAnswer>( return GuaribasAnswer.findOne({
(resolve, reject) => { where: {
GuaribasAnswer.findAll({ instanceId: instanceId,
where: { answerId: answerId
instanceId: instanceId, }
answerId: answerId })
}
}).then((item: GuaribasAnswer[]) => {
resolve(item[0])
}).error((reason) => {
reject(reason)
})
})
} }
async getAnswerByText( async getAnswerByText(
@ -291,7 +296,7 @@ export class KBService {
static getSubjectItemsSeparatedBySpaces(subjects: GuaribasSubject[]) { static getSubjectItemsSeparatedBySpaces(subjects: GuaribasSubject[]) {
let out = [] let out = []
subjects.forEach(subject => { subjects.forEach(subject => {
out.push(subject.title) out.push(subject.internalId)
}) })
return out.join(" ") return out.join(" ")
} }
@ -300,66 +305,37 @@ export class KBService {
instanceId: number, instanceId: number,
parentId: number parentId: number
): Promise<GuaribasSubject[]> { ): Promise<GuaribasSubject[]> {
return new Promise<GuaribasSubject[]>( var where = { parentSubjectId: parentId, instanceId: instanceId }
(resolve, reject) => { return GuaribasSubject.findAll({
var where = { parentSubjectId: parentId, instanceId: instanceId } where: where
GuaribasSubject.findAll({ })
where: where
})
.then((values: GuaribasSubject[]) => {
resolve(values)
})
.error(reason => {
reject(reason)
})
})
} }
async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> { async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> {
return new Promise<GuaribasQuestion[]>( let where = {
(resolve, reject) => { from: from
}
let where = { if (subjects) {
from: from if (subjects[0]) {
} where["subject1"] = subjects[0].internalId
}
if (subjects) { if (subjects[1]) {
if (subjects[0]) { where["subject2"] = subjects[1].internalId
where["subject1"] = subjects[0].title }
}
if (subjects[1]) { if (subjects[2]) {
where["subject2"] = subjects[1].title where["subject3"] = subjects[2].internalId
} }
if (subjects[2]) { if (subjects[3]) {
where["subject3"] = subjects[2].title where["subject4"] = subjects[3].internalId
} }
}
if (subjects[3]) { return await GuaribasQuestion.findAll({
where["subject4"] = subjects[3].title where: where
} })
}
GuaribasQuestion.findAll({
where: where
})
.then((items: GuaribasQuestion[]) => {
if (!items) items = []
if (items.length == 0) {
resolve([])
} else {
resolve(items)
}
})
.catch(reason => {
if (reason.message.indexOf("no such table: IGBInstance") != -1) {
resolve([])
} else {
reject(reason)
logger.info(`GuaribasServiceError: ${reason}`)
}
})
})
} }
async importKbTabularFile( async importKbTabularFile(
@ -373,6 +349,9 @@ export class KBService {
delimiter: "\t" delimiter: "\t"
} }
let lastQuestion: GuaribasQuestion;
let lastAnswer: GuaribasAnswer;
let data = await parse(file, opts) let data = await parse(file, opts)
return asyncPromise.eachSeries(data, async line => { return asyncPromise.eachSeries(data, async line => {
@ -428,9 +407,11 @@ export class KBService {
instanceId: instanceId, instanceId: instanceId,
content: answer, content: answer,
format: format, format: format,
packageId: packageId packageId: packageId,
prevId: lastQuestion ? lastQuestion.questionId : 0,
}) })
await GuaribasQuestion.create({
let question1 = await GuaribasQuestion.create({
from: from, from: from,
to: to, to: to,
subject1: subject1, subject1: subject1,
@ -443,7 +424,13 @@ export class KBService {
packageId: packageId packageId: packageId
}) })
return Promise.resolve(question) if (lastAnswer && lastQuestion) {
await lastAnswer.updateAttributes({ nextId: lastQuestion.questionId })
}
lastAnswer = answer1
lastQuestion = question1
return Promise.resolve(lastQuestion)
} else { } else {
@ -465,7 +452,7 @@ export class KBService {
} else if (answer.content.length > 140 && } else if (answer.content.length > 140 &&
dc.context._activity.channelId === "webchat") { dc.context._activity.channelId === "webchat") {
const locale = dc.context.activity.locale; const locale = dc.context.activity.locale;
await dc.context.sendActivity(Messages[locale].will_answer_projector) // TODO: Handle rnd. await dc.context.sendActivity(Messages[locale].will_answer_projector) // TODO: Handle rnd.
var html = answer.content var html = answer.content
@ -483,7 +470,13 @@ export class KBService {
}) })
html = marked(answer.content) html = marked(answer.content)
} }
await conversationalService.sendEvent(dc, "play", { playerType: "markdown", data: html }) await conversationalService.sendEvent(dc, "play",
{
playerType: "markdown", data: {
content: html, answer: answer,
prevId: answer.prevId, nextId: answer.nextId
}
})
} else { } else {
await dc.context.sendActivity(answer.content) await dc.context.sendActivity(answer.content)
await conversationalService.sendEvent(dc, "stop", null) await conversationalService.sendEvent(dc, "stop", null)
@ -560,10 +553,8 @@ export class KBService {
else { else {
return Promise.resolve(item) return Promise.resolve(item)
} }
}) })
} }
return doIt(subjects.children, null) return doIt(subjects.children, null)
} }
@ -603,11 +594,11 @@ export class KBService {
) )
let instance = await core.loadInstance(packageObject.botId) let instance = await core.loadInstance(packageObject.botId)
logger.info(`[GBDeployer] Beginning importing: ${localPath}`) logger.info(`[GBDeployer] Importing: ${localPath}`)
let p = await deployer.deployPackageToStorage( let p = await deployer.deployPackageToStorage(
instance.instanceId, instance.instanceId,
packageName) packageName)
await this.importKbPackage(localPath, p, instance) await this.importKbPackage(localPath, p, instance)
logger.info(`[GBDeployer] Finished importing ${localPath}`) logger.info(`[GBDeployer] Finished import of ${localPath}`)
} }
} }