new(whatsapp.gblib): New Teams and WhatsApp support.

This commit is contained in:
Rodrigo Rodriguez 2021-03-03 16:46:18 -03:00
parent bcbc13f3f0
commit ceb5f0aaf9
10 changed files with 223 additions and 73 deletions

View file

@ -80,3 +80,13 @@ GO
ALTER TABLE dbo.GuaribasInstance ADD
translatorEndpoint nvarchar(128) NULL
GO
# 2.0.108
ALTER TABLE [dbo].[GuaribasInstance] DROP COLUMN [agentSystemId]
GO
ALTER TABLE dbo.GuaribasUser ADD
agentSystemId nvarchar(255) NULL,
GO

View file

@ -125,7 +125,7 @@ export class SystemKeywords {
let sec = new SecService();
let user = await sec.getUserFromSystemId(from);
if (!user) {
user = await sec.ensureUser(this.min.instance.instanceId, from, from, null, 'whatsapp', 'from');
user = await sec.ensureUser(this.min.instance.instanceId, from, from, null, 'whatsapp', 'from', null);
}
await sec.updateUserHearOnDialog(user.userId, dialogName);
}

View file

@ -47,6 +47,7 @@ import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsServic
import { MicrosoftAppCredentials } from 'botframework-connector';
import { GBConfigService } from './GBConfigService';
import { CollectionUtil, AzureText } from 'pragmatismo-io-framework';
import { GuaribasUser } from '../../security.gbapp/models';
const urlJoin = require('url-join');
const PasswordGenerator = require('strict-password-generator').default;
const Nexmo = require('nexmo');
@ -851,19 +852,27 @@ export class GBConversationalService {
const users = await service.getAllUsers(min.instance.instanceId);
await CollectionUtil.asyncForEach(users, async user => {
if (user.conversationReference) {
const ref = JSON.parse(user.conversationReference);
MicrosoftAppCredentials.trustServiceUrl(ref.serviceUrl);
await min.bot['createConversation'](ref, async t1 => {
const ref2 = TurnContext.getConversationReference(t1.activity);
await min.bot.continueConversation(ref2, async t2 => {
await t2.sendActivity(message);
});
});
await this.sendOnConversation(min, user, message);
} else {
GBLog.info(`User: ${user.systemUserId} with no conversation ID while broadcasting.`);
}
});
}
/**
*
* Sends a message in a user with an already started conversation (got ConversationReference set)
*/
public async sendOnConversation(min: GBMinInstance, user: GuaribasUser, message: string) {
const ref = JSON.parse(user.conversationReference);
MicrosoftAppCredentials.trustServiceUrl(ref.serviceUrl);
await min.bot['createConversation'](ref, async (t1) => {
const ref2 = TurnContext.getConversationReference(t1.activity);
await min.bot.continueConversation(ref2, async (t2) => {
await t2.sendActivity(message);
});
});
}
public static kmpSearch(pattern, text) {
pattern = pattern.toLowerCase();

View file

@ -307,7 +307,7 @@ export class GBMinService {
const sec = new SecService();
let user = await sec.getUserFromSystemId(id);
if (user === null || user.hearOnDialog) {
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName);
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName, null);
const startDialog = user.hearOnDialog ?
user.hearOnDialog :
@ -760,10 +760,10 @@ export class GBMinService {
});
const service = new KBService(min.core.sequelize);
const data = await service.getFaqBySubjectArray(instance.instanceId, 'faq', undefined);
await min.conversationalService.sendEvent(min, step, 'play', {
playerType: 'bullet',
data: data.slice(0, 10)
});
await min.conversationalService.sendEvent(min, step, 'play', {
playerType: 'bullet',
data: data.slice(0, 10)
});
// This same event is dispatched either to all participants
// including the bot, that is filtered bellow.
@ -779,17 +779,10 @@ export class GBMinService {
member.name,
'',
'web',
member.name
member.name,
null
);
// Required for MSTEAMS handling of persisted conversations.
if (step.context.activity.channelId === 'msteams') {
persistedUser.conversationReference = JSON.stringify(
TurnContext.getConversationReference(context.activity)
);
await persistedUser.save();
}
// Stores conversation associated to the user to group each message.
@ -804,6 +797,18 @@ export class GBMinService {
await min.userProfile.set(step.context, user);
}
user.systemUser = await sec.getUserFromSystemId( user.systemUser.userSystemId);
await min.userProfile.set(step.context, user);
// Required for MSTEAMS handling of persisted conversations.
if (step.context.activity.channelId === 'msteams') {
const conversationReference = JSON.stringify(
TurnContext.getConversationReference(context.activity)
);
await sec.updateConversationReferenceById(user.systemUser.userId, conversationReference);
}
GBLog.info(`User>: text:${context.activity.text} (type: ${context.activity.type}, name: ${context.activity.name}, channelId: ${context.activity.channelId}, value: ${context.activity.value})`);
// Answer to specific BOT Framework event conversationUpdate to auto start dialogs.
@ -911,6 +916,8 @@ export class GBMinService {
*/
private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) {
const sec = new SecService();
// Removes <at>Bot Id</at> from MS Teams.
context.activity.text = context.activity.text.trim();
@ -1055,7 +1062,7 @@ export class GBMinService {
if (detectLanguage || !locale) {
locale = await min.conversationalService.getLanguage(min, text);
if (systemUser.locale != locale) {
const sec = new SecService();
user.systemUser = await sec.updateUserLocale(systemUser.userId, locale);
await min.userProfile.set(step.context, user);
}
@ -1092,28 +1099,39 @@ export class GBMinService {
context.activity.originalText = originalText;
GBLog.info(`Final text ready for NLP/Search/.gbapp: ${text}.`);
// If there is a dialog in course, continue to the next step.
if (step.activeDialog !== undefined) {
await step.continueDialog();
if (user.systemUser.agentMode === 'self') {
const manualUser = await sec.getUserFromAgentSystemId(user.systemUser.userSystemId);
} else {
GBLog.info(`HUMAN AGENT (${user.systemUser.userSystemId}) TO USER ${manualUser.userSystemId}: ${text}`);
await min.whatsAppDirectLine.sendToDeviceEx(manualUser.userSystemId, `${manualUser.agentSystemId}: ${text}`, locale);
let nextDialog = null;
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
nextDialog = await e.onExchangeData(min, 'handleAnswer', {
query: text,
step: step,
notTranslatedQuery: originalText,
message: message ? message['dataValues'] : null,
user: user ? user.dataValues : null
}
else {
// If there is a dialog in course, continue to the next step.
if (step.activeDialog !== undefined) {
await step.continueDialog();
} else {
let nextDialog = null;
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
nextDialog = await e.onExchangeData(min, 'handleAnswer', {
query: text,
step: step,
notTranslatedQuery: originalText,
message: message ? message['dataValues'] : null,
user: user ? user.dataValues : null
});
});
});
await step.beginDialog(nextDialog ? nextDialog : '/answer', {
query: text,
user: user ? user.dataValues : null,
message: message
});
await step.beginDialog(nextDialog ? nextDialog : '/answer', {
query: text,
user: user ? user.dataValues : null,
message: message
});
}
}
}
}

View file

@ -75,14 +75,25 @@ export class FeedbackDialog extends IGBDialog {
const locale = step.context.activity.locale;
const sec = new SecService();
const from = step.context.activity.from.id;
let from = step.context.activity.from.id;
await min.conversationalService.sendText(min, step, Messages[locale].please_wait_transfering);
const agentSystemId = await sec.assignHumanAgent(from, min.instance.instanceId);
await min.whatsAppDirectLine.sendToDevice(agentSystemId,
Messages[locale].notify_agent(step.context.activity.from.name));
const user = await min.userProfile.get(step.context, {});
user.systemUser = await sec.getUserFromAgentSystemId(agentSystemId);
await min.userProfile.set(step.context, user);
if (agentSystemId.charAt(2) === ":") { // Agent is from Teams.
const agent = await sec.getUserFromSystemId(agentSystemId);
await min.conversationalService['sendOnConversation'](min, agent,
Messages[locale].notify_agent(step.context.activity.from.name));
}
else {
await min.whatsAppDirectLine.sendToDevice(agentSystemId, Messages[locale].notify_agent(step.context.activity.from.name));
}
return await step.next();
}
])
@ -95,10 +106,65 @@ export class FeedbackDialog extends IGBDialog {
const locale = step.context.activity.locale;
const sec = new SecService();
const from = step.context.activity.from.id;
const userSystemId = step.context.activity.from.id;
const user = await min.userProfile.get(step.context, {});
if (user.systemUser.agentMode === 'self') {
const manualUser = await sec.getUserFromAgentSystemId(userSystemId);
await min.whatsAppDirectLine.sendToDeviceEx(manualUser.userSystemId,
Messages[locale].notify_end_transfer(min.instance.botId), locale);
if (userSystemId.charAt(2) === ":") { // Agent is from Teams.
await min.conversationalService.sendText(min, step, Messages[locale].notify_end_transfer(min.instance.botId));
}
else {
await min.whatsAppDirectLine.sendToDeviceEx(userSystemId,
Messages[locale].notify_end_transfer(min.instance.botId), locale);
}
await sec.updateHumanAgent(userSystemId, min.instance.instanceId, null);
await sec.updateHumanAgent(manualUser.userSystemId, min.instance.instanceId, null);
user.systemUser = await sec.getUserFromSystemId(userSystemId);
await min.userProfile.set(step.context, user);
}
else if (user.systemUser.agentMode === 'human') {
const agent = await sec.getUserFromSystemId(user.systemUser.agentSystemId);
await min.whatsAppDirectLine.sendToDeviceEx(user.systemUser.userSystemId,
Messages[locale].notify_end_transfer(min.instance.botId), locale);
if (user.systemUser.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
await min.conversationalService.sendText(min, step, Messages[locale].notify_end_transfer(min.instance.botId));
}
else {
await min.whatsAppDirectLine.sendToDeviceEx(user.systemUser.agentSystemId,
Messages[locale].notify_end_transfer(min.instance.botId), locale);
}
await sec.updateHumanAgent(user.systemUser.userSystemId, min.instance.instanceId, null);
await sec.updateHumanAgent(agent.userSystemId, min.instance.instanceId, null);
user.systemUser = await sec.getUserFromSystemId(userSystemId);
await min.userProfile.set(step.context, user);
}
else
{
if (user.systemUser.userSystemId.charAt(2) === ":") { // Agent is from Teams.
await min.conversationalService.sendText(min, step, 'Nenhum atendimento em andamento.');
}
else {
await min.whatsAppDirectLine.sendToDeviceEx(user.systemUser.userSystemId,
'Nenhum atendimento em andamento.');
}
}
await sec.updateCurrentAgent(from, min.instance.instanceId, null);
await min.conversationalService.sendText(min, step, Messages[locale].notify_end_transfer(min.instance.botId));
return await step.next();
}
@ -149,7 +215,7 @@ export class FeedbackDialog extends IGBDialog {
} else {
const message = min.core.getParam<string>(min.instance, 'Feedback Improve Message',
Messages[fixedLocale].we_will_improve); // TODO: Improve to be multi-language.
Messages[fixedLocale].we_will_improve); // TODO: Improve to be multi-language.
await min.conversationalService.sendText(min, step, message);
}

View file

@ -25,7 +25,8 @@ export const Messages = {
great_thanks: 'Ótimo, obrigado por contribuir com sua resposta.',
please_no_bad_words: 'Por favor, sem palavrões!',
please_wait_transfering: 'Por favor, aguarde enquanto eu localizo alguém para te atender.',
notify_agent: (name) => `Existe um novo atendimento para *${name}*, por favor, responda aqui mesmo para a pessoa. Para finalizar, digite /qt.`
notify_agent: (name) => `Existe um novo atendimento para *${name}*, por favor, responda aqui mesmo para a pessoa. Para finalizar, digite /qt.`,
notify_end_transfer: (botName) => `Falando novamente com o bot ${botName}.`
}
};

View file

@ -113,7 +113,7 @@ export class AskDialog extends IGBDialog {
let sec = new SecService();
const member = step.context.activity.from;
const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name);
const user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name, null);
let handled = false;
let nextDialog = null;

View file

@ -78,7 +78,7 @@ export class GuaribasUser extends Model<GuaribasUser> {
@BelongsTo(() => GuaribasInstance)
public instance: GuaribasInstance;
@Column(DataType.STRING(16))
@Column(DataType.STRING(255))
public agentSystemId: string;
@Column(DataType.DATE)

View file

@ -39,7 +39,8 @@ export class SecService extends GBService {
userName: string,
address: string,
channelName: string,
displayName: string
displayName: string,
email: string
): Promise<GuaribasUser> {
let user = await GuaribasUser.findOne({
where: {
@ -55,7 +56,7 @@ export class SecService extends GBService {
user.userSystemId = userSystemId;
user.userName = userName;
user.displayName = displayName;
user.email = userName;
user.email = email;
user.defaultChannel = channelName;
return await user.save();
@ -82,6 +83,14 @@ export class SecService extends GBService {
await user.save();
}
public async updateConversationReferenceById(userId: number, conversationReference: string) {
const options = { where: { userId: userId } };
const user = await GuaribasUser.findOne(options);
user.conversationReference = conversationReference;
await user.save();
}
public async updateUserLocale(userId: number, locale: any): Promise<GuaribasUser> {
const user = await GuaribasUser.findOne({
where: {
@ -115,14 +124,18 @@ export class SecService extends GBService {
return await user.save();
}
public async updateCurrentAgent(
/**
* Finds and update user agent information to a next available person.
*/
public async updateHumanAgent(
userSystemId: string,
instanceId: number,
agentSystemId: string
): Promise<GuaribasUser> {
const user = await GuaribasUser.findOne({
where: {
userSystemId: userSystemId
userSystemId: userSystemId,
instanceId: instanceId
}
});
@ -190,7 +203,7 @@ export class SecService extends GBService {
}
});
await this.updateCurrentAgent(userSystemId, instanceId, agentSystemId);
await this.updateHumanAgent(userSystemId, instanceId, agentSystemId);
return agentSystemId;
}

View file

@ -178,7 +178,7 @@ export class WhatsappDirectLine extends GBService {
const sec = new SecService();
const user = await sec.ensureUser(this.min.instance.instanceId, id,
senderName, '', 'whatsapp', senderName);
senderName, '', 'whatsapp', senderName, null);
const locale = user.locale ? user.locale : 'pt';
if (message.type === 'ptt') {
@ -205,12 +205,22 @@ export class WhatsappDirectLine extends GBService {
const conversationId = WhatsappDirectLine.conversationIds[from];
const client = await this.directLineClient;
if (user.agentMode === 'self') {
const manualUser = await sec.getUserFromAgentSystemId(id);
// Check if this message is from a Human Agent itself.
if (user.agentMode === 'self') {
// Check if there is someone being handled by this Human Agent.
const manualUser = await sec.getUserFromAgentSystemId(id);
if (manualUser === null) {
await sec.updateCurrentAgent(id, this.min.instance.instanceId, null);
await sec.updateHumanAgent(id, this.min.instance.instanceId, null);
} else {
const agent = await sec.getUserFromSystemId(user.agentSystemId);
const cmd = '/reply ';
if (text.startsWith(cmd)) {
const filename = text.substr(cmd.length);
@ -218,37 +228,60 @@ export class WhatsappDirectLine extends GBService {
if (message === null) {
await this.sendToDeviceEx(user.userSystemId, `File ${filename} not found in any .gbkb published. Check the name or publish again the associated .gbkb.`,
locale);
locale);
} else {
await this.min.conversationalService.sendMarkdownToMobile(this.min, null, user.userSystemId, message);
}
} else if (text === '/qt') {
// TODO: Transfers only in pt-br for now.
await this.sendToDeviceEx(manualUser.userSystemId,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(user.agentSystemId,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await sec.updateCurrentAgent(manualUser.userSystemId, this.min.instance.instanceId, null);
if (user.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
await this.min.conversationalService['sendOnConversation'](this.min, agent, Messages[this.locale].notify_end_transfer(this.min.instance.botId));
}
else {
await this.sendToDeviceEx(user.agentSystemId,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
}
await sec.updateHumanAgent(manualUser.userSystemId, this.min.instance.instanceId, null);
await sec.updateHumanAgent(user.agentSystemId, this.min.instance.instanceId, null);
} else {
GBLog.info(`HUMAN AGENT (${id}) TO USER ${manualUser.userSystemId}: ${text}`);
this.sendToDeviceEx(manualUser.userSystemId, `${manualUser.agentSystemId}: ${text}`, locale);
await this.sendToDeviceEx(manualUser.userSystemId, `${manualUser.agentSystemId}: ${text}`, locale);
}
}
} else if (user.agentMode === 'human') {
const agent = await sec.getUserFromSystemId(user.agentSystemId);
if (text === '/t') {
await this.sendToDeviceEx(user.userSystemId, `Você já está sendo atendido por ${agent.userSystemId}.`, locale);
} else if (text === '/qt' || text === 'Sair' || text === 'Fechar') {
// TODO: Transfers only in pt-br for now.
await this.sendToDeviceEx(id,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(user.agentSystemId, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await sec.updateCurrentAgent(id, this.min.instance.instanceId, null);
if (user.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
await this.min.conversationalService['sendOnConversation'](this.min, agent, Messages[this.locale].notify_end_transfer(this.min.instance.botId));
}
else {
await this.sendToDeviceEx(user.agentSystemId, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
}
await sec.updateHumanAgent(id, this.min.instance.instanceId, null);
} else {
GBLog.info(`USER (${id}) TO AGENT ${user.userSystemId}: ${text}`);
this.sendToDeviceEx(user.agentSystemId, `Bot: ${this.min.instance.botId}\n${id}: ${text}`, locale);
GBLog.info(`USER (${id}) TO AGENT ${agent.userSystemId}: ${text}`);
if (user.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
await this.min.conversationalService['sendOnConversation'](this.min, agent, text);
}
else {
await this.sendToDeviceEx(user.agentSystemId, `Bot: ${this.min.instance.botId}\n${id}: ${text}`, locale);
}
}
} else if (user.agentMode === 'bot' || user.agentMode === null || user.agentMode === undefined) {
@ -451,7 +484,7 @@ export class WhatsappDirectLine extends GBService {
}
}
private async sendToDeviceEx(to, text, locale) {
public async sendToDeviceEx(to, text, locale) {
const minBoot = GBServer.globals.minBoot as any;
text = await minBoot.conversationalService.translate(