Finalização da Implementaçao do dialogflow

pull/20/head
adriano 2022-11-08 17:23:13 -03:00
parent 9bf1850405
commit 1bdd4adaba
54 changed files with 2076 additions and 421 deletions

View File

@ -15,6 +15,9 @@
"author": "",
"license": "MIT",
"dependencies": {
"@google-cloud/dialogflow": "^4.6.0",
"actions-on-google": "^3.0.0",
"axios": "^0.27.2",
"@sentry/node": "^5.29.2",
"@types/pino": "^6.3.4",
"bcryptjs": "^2.4.3",

View File

@ -13,10 +13,12 @@ import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import AppError from "../errors/AppError";
import { searchContactCache } from '../helpers/ContactsCache'
import { off } from "process";
import GetContactService from "../services/ContactServices/GetContactService";
import ToggleUseQueuesContactService from "../services/ContactServices/ToggleUseQueuesContactService";
import ToggleUseDialogflowContactService from "../services/ContactServices/ToggleUseDialogflowContactService";
type IndexQuery = {
@ -32,9 +34,15 @@ interface ContactData {
name: string;
number: string;
email?: string;
useDialogflow: boolean;
extraInfo?: ExtraInfo[];
}
type IndexGetContactQuery = {
name: string;
number: string;
};
export const index = async (req: Request, res: Response): Promise<Response> => {
let { searchParam, pageNumber } = req.query as IndexQuery;
@ -68,6 +76,17 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
return res.json({ contacts, count, hasMore });
};
export const getContact = async (req: Request, res: Response): Promise<Response> => {
const { name, number } = req.body as IndexGetContactQuery;
const contact = await GetContactService({
name,
number
});
return res.status(200).json(contact);
};
export const store = async (req: Request, res: Response): Promise<Response> => {
const newContact: ContactData = req.body;
newContact.number = newContact.number.replace("-", "").replace(" ", "");
@ -94,11 +113,13 @@ export const store = async (req: Request, res: Response): Promise<Response> => {
let number = validNumber
let email = newContact.email
let extraInfo = newContact.extraInfo
let useDialogflow = newContact.useDialogflow
const contact = await CreateContactService({
name,
number,
email,
useDialogflow,
extraInfo,
profilePicUrl
});
@ -173,6 +194,40 @@ export const remove = async (
};
export const toggleUseQueue = async (
req: Request,
res: Response
): Promise<Response> => {
const { contactId } = req.params;
const contact = await ToggleUseQueuesContactService({ contactId });
const io = getIO();
io.emit("contact", {
action: "update",
contact
});
return res.status(200).json(contact);
};
export const toggleUseDialogflow = async (
req: Request,
res: Response
): Promise<Response> => {
const { contactId } = req.params;
const contact = await ToggleUseDialogflowContactService({ contactId });
const io = getIO();
io.emit("contact", {
action: "update",
contact
});
return res.status(200).json(contact);
};
export const contacsBulkInsertOnQueue = async (req: Request, res: Response): Promise<Response> => {

View File

@ -0,0 +1,85 @@
import { Request, Response } from "express";
import { getIO } from "../libs/socket";
import CreateDialogflowService from "../services/DialogflowServices/CreateDialogflowService";
import DeleteDialogflowService from "../services/DialogflowServices/DeleteDialogflowService";
import ListDialogflowsService from "../services/DialogflowServices/ListDialogflowService";
import ShowDialogflowService from "../services/DialogflowServices/ShowDialogflowService";
import TestSessionDialogflowService from "../services/DialogflowServices/TestSessionDialogflowService";
import UpdateDialogflowService from "../services/DialogflowServices/UpdateDialogflowService";
export const index = async (req: Request, res: Response): Promise<Response> => {
const dialogflows = await ListDialogflowsService();
return res.status(200).json(dialogflows);
};
export const store = async (req: Request, res: Response): Promise<Response> => {
const { name, projectName, jsonContent, language } = req.body;
const dialogflow = await CreateDialogflowService({ name, projectName, jsonContent, language });
const io = getIO();
io.emit("dialogflow", {
action: "update",
dialogflow
});
return res.status(200).json(dialogflow);
};
export const show = async (req: Request, res: Response): Promise<Response> => {
const { dialogflowId } = req.params;
const dialogflow = await ShowDialogflowService(dialogflowId);
return res.status(200).json(dialogflow);
};
export const update = async (
req: Request,
res: Response
): Promise<Response> => {
const { dialogflowId } = req.params;
const dialogflowData = req.body;
const dialogflow = await UpdateDialogflowService({dialogflowData, dialogflowId });
const io = getIO();
io.emit("dialogflow", {
action: "update",
dialogflow
});
return res.status(201).json(dialogflow);
};
export const remove = async (
req: Request,
res: Response
): Promise<Response> => {
const { dialogflowId } = req.params;
await DeleteDialogflowService(dialogflowId);
const io = getIO();
io.emit("dialogflow", {
action: "delete",
dialogflowId: +dialogflowId
});
return res.status(200).send();
};
export const testSession = async (req: Request, res: Response): Promise<Response> => {
const { projectName, jsonContent, language } = req.body;
const response = await TestSessionDialogflowService({ projectName, jsonContent, language });
const io = getIO();
io.emit("dialogflow", {
action: "testSession",
response
});
return res.status(200).json(response);
};

View File

@ -14,6 +14,8 @@ import QuickAnswer from "../models/QuickAnswer";
import SchedulingNotify from "../models/SchedulingNotify";
import StatusChatEnd from "../models/StatusChatEnd";
import UserOnlineTime from "../models/UserOnlineTime";
import Dialogflow from "../models/Dialogflow";
// eslint-disable-next-line
const dbConfig = require("../config/database");
// import dbConfig from "../config/database";
@ -36,6 +38,7 @@ const models = [
SchedulingNotify,
StatusChatEnd,
UserOnlineTime,
Dialogflow,
];
sequelize.addModels(models);

View File

@ -0,0 +1,44 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.createTable("Dialogflows", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
projectName: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
jsonContent: {
type: DataTypes.TEXT,
allowNull: false,
},
language: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false
}
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.dropTable("Dialogflows");
}
};

View File

@ -0,0 +1,16 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Queues", "dialogflowId", {
type: DataTypes.INTEGER,
references: { model: "Dialogflows", key: "id" },
onUpdate: "CASCADE",
onDelete: "SET NULL"
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Queues", "dialogflowId");
}
};

View File

@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Contacts", "useDialogflow", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Contacts", "useDialogflow");
}
};

View File

@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Contacts", "useQueues", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Contacts", "useQueues");
}
};

View File

@ -1,37 +1,70 @@
const fsPromises = require("fs/promises");
const fs = require('fs')
import dir from 'path';
import fs from 'fs';
import os from 'os';
import ListUsersService from "../services/UserServices/ListUsersService"
const _botIsOnQueue = async (botName: string) => {
const { users, count, hasMore } = await ListUsersService({searchParam:`${botName}`,pageNumber:1});
let botIsOnQueue = false
let userIdBot = null
let queueId = null
const botInfoFile = dir.join(os.tmpdir(), `botInfo.json`);
if(users.length > 0){
console.log('The bot botInfoFile: ', botInfoFile)
try {
console.log('----------------- bot queue id: ', Object(users)[0]["queues"][0].id)
queueId = Object(users)[0]["queues"][0].id;
userIdBot = Object(users)[0].id
botIsOnQueue = true
try {
}catch(err){
if (fs.existsSync(botInfoFile)) {
console.log('O usuário botqueue não está em nenhuma fila err: ',err)
console.log('botInfo.json file exists');
}
const botInfo = fs.readFileSync(botInfoFile, {encoding:'utf8', flag:'r'});
}
else{
console.log('Usuário botqueue não existe!')
return JSON.parse(botInfo)
} else {
console.log('botInfo.json file not found!');
}
return { userIdBot: userIdBot, botQueueId: queueId, isOnQueue: botIsOnQueue }
} catch (error) {
console.log('There was an error on try to read the botInfo.json file: ',error)
}
console.log('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ')
const { users, count, hasMore } = await ListUsersService({ searchParam: `${botName}`, pageNumber: 1 });
let botIsOnQueue = false
let userIdBot = null
let queueId = null
if (users.length > 0) {
try {
console.log('----------------- bot queue id: ', Object(users)[0]["queues"][0].id)
queueId = Object(users)[0]["queues"][0].id;
userIdBot = Object(users)[0].id
botIsOnQueue = true
fs.writeFileSync(botInfoFile, JSON.stringify({ userIdBot: userIdBot, botQueueId: queueId, isOnQueue: botIsOnQueue }), "utf8");
} catch (err) {
console.log('O usuário botqueue não está em nenhuma fila err: ', err)
}
}
else {
console.log('Usuário botqueue não existe!')
fs.writeFileSync(botInfoFile, JSON.stringify({ isOnQueue: false, botQueueId: 0, userIdBot: 0 }), "utf8");
}
export default _botIsOnQueue;
return { userIdBot: userIdBot, botQueueId: queueId, isOnQueue: botIsOnQueue }
}
export default _botIsOnQueue;

View File

@ -0,0 +1,33 @@
const fsPromises = require("fs/promises");
const fs = require('fs')
import axios from 'axios';
import * as https from "https";
const endPointQuery = async (url: string) => {
let response:any = null
try {
const httpsAgent = new https.Agent({ rejectUnauthorized: false, });
// const url = 'https://sos.espacolaser.com.br/api/whatsapp/ticket/R32656'
response = await axios.get(url, {
httpsAgent,
headers: {
'x-access-token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOnsiaWQiOjEsInJvbGUiOiJjbGllbnQiLCJob3N0Ijoic29zLmVzcGFjb2xhc2VyLmNvbS5iciIsInRlbmFudCI6ImVzcGFjb2xhc2VyIiwibmFtZSI6IlNFTlNSLklUIiwiY29tcGFueSI6eyJpZCI6NDR9fSwiZGF0ZSI6MTY2MTI2MjY0MywiaWF0IjoxNjYxMjYyNjQzLCJleHAiOjE3NDc2NjI2NDN9.zf91OmRs4_C7B8OlVpLLrQMiRBYc7edP4qAdH_hqxpk',
'Origin': 'espacolaser'
}
});
console.log(`TEST URL CLIENT GET ROUTE: ${url} | STATUS CODE: ${response.status}`);
} catch (error) {
console.error(error);
}
return response
}
export default endPointQuery;

View File

@ -0,0 +1,20 @@
import dir from 'path';
import fs from 'fs';
import os from 'os';
const deleteFileFromTMP = (name_ext: string) =>{
const botInfoFile = dir.join(os.tmpdir(), name_ext);
try {
fs.unlinkSync(botInfoFile);
console.log(`${name_ext} file deleted!`);
} catch (error) {
console.log(`Can't delete ${name_ext} file: `,error)
}
}
export default deleteFileFromTMP;

View File

@ -41,6 +41,14 @@ class Contact extends Model<Contact> {
@Column
isGroup: boolean;
@Default(true)
@Column
useQueues: boolean;
@Default(true)
@Column
useDialogflow: boolean;
@CreatedAt
createdAt: Date;

View File

@ -0,0 +1,45 @@
import {
Table,
Column,
CreatedAt,
UpdatedAt,
Model,
DataType,
PrimaryKey,
HasMany,
AutoIncrement
} from "sequelize-typescript";
import Queue from "./Queue";
@Table
class Dialogflow extends Model<Dialogflow> {
@PrimaryKey
@AutoIncrement
@Column
id: number;
@Column(DataType.TEXT)
name: string;
@Column(DataType.TEXT)
projectName: string;
@Column(DataType.TEXT)
jsonContent: string;
@Column(DataType.TEXT)
language: string;
@CreatedAt
@Column(DataType.DATE(6))
createdAt: Date;
@UpdatedAt
@Column(DataType.DATE(6))
updatedAt: Date;
@HasMany(() => Queue)
queues: Queue[]
}
export default Dialogflow;

View File

@ -8,13 +8,16 @@ import {
AutoIncrement,
AllowNull,
Unique,
BelongsToMany
BelongsToMany,
BelongsTo,
ForeignKey
} from "sequelize-typescript";
import User from "./User";
import UserQueue from "./UserQueue";
import Whatsapp from "./Whatsapp";
import WhatsappQueue from "./WhatsappQueue";
import Dialogflow from "./Dialogflow";
@Table
class Queue extends Model<Queue> {
@ -36,6 +39,13 @@ class Queue extends Model<Queue> {
@Column
greetingMessage: string;
@ForeignKey(() => Dialogflow)
@Column
dialogflowId: number;
@BelongsTo(() => Dialogflow)
dialogflow: Dialogflow;
@CreatedAt
createdAt: Date;

View File

@ -18,6 +18,10 @@ contactRoutes.post("/contacts", isAuth, ContactController.store);
contactRoutes.put("/contacts/:contactId", isAuth, ContactController.update);
contactRoutes.put("/contacts/toggleUseQueues/:contactId", isAuth, ContactController.toggleUseQueue);
contactRoutes.put("/contacts/toggleUseDialogflow/:contactId", isAuth, ContactController.toggleUseDialogflow);
contactRoutes.delete("/contacts/:contactId", isAuth, ContactController.remove);
export default contactRoutes;

View File

@ -0,0 +1,20 @@
import { Router } from "express";
import isAuth from "../middleware/isAuth";
import * as DialogflowController from "../controllers/DialogflowController";
const dialogflowRoutes = Router();
dialogflowRoutes.get("/dialogflow", isAuth, DialogflowController.index);
dialogflowRoutes.post("/dialogflow", isAuth, DialogflowController.store);
dialogflowRoutes.get("/dialogflow/:dialogflowId", isAuth, DialogflowController.show);
dialogflowRoutes.put("/dialogflow/:dialogflowId", isAuth, DialogflowController.update);
dialogflowRoutes.delete("/dialogflow/:dialogflowId", isAuth, DialogflowController.remove);
dialogflowRoutes.post("/dialogflow/testsession", isAuth, DialogflowController.testSession);
export default dialogflowRoutes;

View File

@ -13,6 +13,7 @@ import quickAnswerRoutes from "./quickAnswerRoutes";
import reportRoutes from "./reportRoutes";
import schedulingNotifiyRoutes from "./SchedulingNotifyRoutes";
import statusChatEndRoutes from "./statusChatEndRoutes";
import dialogflowRoutes from "./dialogflowRoutes";
const routes = Router();
@ -31,5 +32,6 @@ routes.use(quickAnswerRoutes);
routes.use(schedulingNotifiyRoutes);
routes.use(reportRoutes);
routes.use(statusChatEndRoutes);
routes.use(dialogflowRoutes);
export default routes;

View File

@ -3,6 +3,7 @@ import Contact from "../../models/Contact";
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
interface ExtraInfo {
name: string;
value: string;
@ -12,6 +13,7 @@ interface Request {
name: string;
number: string;
email?: string;
useDialogflow?: boolean;
profilePicUrl?: string;
extraInfo?: ExtraInfo[];
}
@ -20,6 +22,7 @@ const CreateContactService = async ({
name,
number,
email = "",
useDialogflow,
extraInfo = []
}: Request): Promise<Contact> => {
const numberExists = await Contact.findOne({
@ -35,6 +38,7 @@ const CreateContactService = async ({
name,
number,
email,
useDialogflow,
extraInfo
},
{
@ -47,7 +51,6 @@ const CreateContactService = async ({
await createOrUpdateContactCache(`contact:${contact.id}`, {id: contact.id, name, number, profilePicUrl:'', isGroup:'false', extraInfo, email })
//
return contact;
};

View File

@ -0,0 +1,38 @@
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
import CreateContactService from "./CreateContactService";
interface ExtraInfo {
name: string;
value: string;
}
interface Request {
name: string;
number: string;
email?: string;
profilePicUrl?: string;
extraInfo?: ExtraInfo[];
}
const GetContactService = async ({ name, number }: Request): Promise<Contact> => {
const numberExists = await Contact.findOne({
where: { number }
});
if (!numberExists) {
const contact = await CreateContactService({
name,
number,
})
if (contact == null)
throw new AppError("CONTACT_NOT_FOUND")
else
return contact
}
return numberExists
};
export default GetContactService;

View File

@ -0,0 +1,34 @@
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
interface Request {
contactId: string;
}
const ToggleUseDialogflowContactService = async ({
contactId
}: Request): Promise<Contact> => {
const contact = await Contact.findOne({
where: { id: contactId },
attributes: ["id", "useDialogflow"]
});
if (!contact) {
throw new AppError("ERR_NO_CONTACT_FOUND", 404);
}
const useDialogflow = contact.useDialogflow ? false : true;
await contact.update({
useDialogflow
});
await contact.reload({
attributes: ["id", "name", "number", "email", "profilePicUrl", "useQueues", "useDialogflow"],
include: ["extraInfo"]
});
return contact;
};
export default ToggleUseDialogflowContactService;

View File

@ -0,0 +1,34 @@
import AppError from "../../errors/AppError";
import Contact from "../../models/Contact";
interface Request {
contactId: string;
}
const ToggleUseQueuesContactService = async ({
contactId
}: Request): Promise<Contact> => {
const contact = await Contact.findOne({
where: { id: contactId },
attributes: ["id", "useQueues"]
});
if (!contact) {
throw new AppError("ERR_NO_CONTACT_FOUND", 404);
}
const useQueues = contact.useQueues ? false : true;
await contact.update({
useQueues
});
await contact.reload({
attributes: ["id", "name", "number", "email", "profilePicUrl", "useQueues", "useDialogflow"],
include: ["extraInfo"]
});
return contact;
};
export default ToggleUseQueuesContactService;

View File

@ -0,0 +1,76 @@
import * as Yup from "yup";
import AppError from "../../errors/AppError";
import Dialogflow from "../../models/Dialogflow";
interface Request {
name: string;
projectName: string;
jsonContent: string;
language: string;
}
const CreateDialogflowService = async ({
name,
projectName,
jsonContent,
language
}: Request): Promise<Dialogflow> => {
const schema = Yup.object().shape({
name: Yup.string()
.required()
.min(2)
.test(
"Check-name",
"This DialogFlow name is already used.",
async value => {
if (!value) return false;
const nameExists = await Dialogflow.findOne({
where: { name: value }
});
return !nameExists;
}
),
projectName: Yup.string()
.required()
.min(2)
.test(
"Check-name",
"This DialogFlow projectName is already used.",
async value => {
if (!value) return false;
const nameExists = await Dialogflow.findOne({
where: { projectName: value }
});
return !nameExists;
}
),
jsonContent: Yup.string()
.required()
,
language: Yup.string()
.required()
.min(2)
});
try {
await schema.validate({ name, projectName, jsonContent, language });
} catch (err) {
throw new AppError(err.message);
}
const dialogflow = await Dialogflow.create(
{
name,
projectName,
jsonContent,
language
}
);
return dialogflow;
};
export default CreateDialogflowService;

View File

@ -0,0 +1,33 @@
import { SessionsClient } from "@google-cloud/dialogflow";
import Dialogflow from "../../models/Dialogflow";
import dir from 'path';
import fs from 'fs';
import os from 'os';
import { logger } from "../../utils/logger";
const sessions : Map<number, SessionsClient> = new Map<number, SessionsClient>();
const createDialogflowSession = async (id:number, projectName:string, jsonContent:string) : Promise<SessionsClient | undefined> => {
if(sessions.has(id)) {
return sessions.get(id);
}
const keyFilename = dir.join(os.tmpdir(), `whaticket_${id}.json`);
console.log('keyFilename: ',keyFilename)
logger.info(`Openig new dialogflow session #${projectName} in '${keyFilename}'`)
await fs.writeFileSync(keyFilename, jsonContent);
const session = new SessionsClient({ keyFilename });
sessions.set(id, session);
return session;
}
const createDialogflowSessionWithModel = async (model: Dialogflow) : Promise<SessionsClient | undefined> => {
return createDialogflowSession(model.id, model.projectName, model.jsonContent);
}
export { createDialogflowSession, createDialogflowSessionWithModel };

View File

@ -0,0 +1,16 @@
import Dialogflow from "../../models/Dialogflow";
import AppError from "../../errors/AppError";
const DeleteDialogflowService = async (id: string): Promise<void> => {
const dialogflow = await Dialogflow.findOne({
where: { id }
});
if (!dialogflow) {
throw new AppError("ERR_NO_DIALOG_FOUND", 404);
}
await dialogflow.destroy();
};
export default DeleteDialogflowService;

View File

@ -0,0 +1,9 @@
import DialogFLow from "../../models/Dialogflow";
const ListDialogflowService = async (): Promise<DialogFLow[]> => {
const dialogFLows = await DialogFLow.findAll();
return dialogFLows;
};
export default ListDialogflowService;

View File

@ -0,0 +1,64 @@
import * as Sentry from "@sentry/node";
import { SessionsClient } from "@google-cloud/dialogflow";
import { logger } from "../../utils/logger";
async function detectIntent(
sessionClient:SessionsClient,
projectId:string,
sessionId:string,
query:string,
languageCode:string
) {
const sessionPath = sessionClient.projectAgentSessionPath(
projectId,
sessionId
);
const request = {
session: sessionPath,
queryInput: {
text: {
text: query,
languageCode: languageCode,
},
},
};
const responses = await sessionClient.detectIntent(request);
return responses[0];
}
async function queryDialogFlow(
sessionClient:SessionsClient,
projectId:string,
sessionId:string,
query:string,
languageCode:string
) : Promise<any | null> {
let intentResponse;
try {
intentResponse = await detectIntent(
sessionClient,
projectId,
sessionId,
query,
languageCode
);
const responses = intentResponse?.queryResult?.fulfillmentMessages;
if (responses?.length === 0) {
return null;
} else {
return responses;
}
} catch (error) {
Sentry.captureException(error);
logger.error(`Error handling whatsapp message: Err: ${error}`);
}
return null;
}
export {queryDialogFlow}

View File

@ -0,0 +1,15 @@
import Dialogflow from "../../models/Dialogflow";
import AppError from "../../errors/AppError";
const ShowDialogflowService = async (id: string | number): Promise<Dialogflow> => {
const dialogflow = await Dialogflow.findByPk(id);
if (!dialogflow) {
throw new AppError("ERR_NO_DIALOG_FOUND", 404);
}
return dialogflow;
};
export default ShowDialogflowService;

View File

@ -0,0 +1,70 @@
import * as Yup from "yup";
import AppError from "../../errors/AppError";
import { queryDialogFlow } from "./QueryDialogflow";
import { createDialogflowSession } from "./CreateSessionDialogflow";
interface Request {
projectName: string;
jsonContent: string;
language: string;
}
interface Response {
messages: string[];
}
const TestDialogflowSession = async ({
projectName,
jsonContent,
language
}: Request): Promise<Response | null> => {
const schema = Yup.object().shape({
projectName: Yup.string()
.required()
.min(2),
jsonContent: Yup.string()
.required(),
language: Yup.string()
.required()
.min(2)
});
try {
await schema.validate({ projectName, jsonContent, language });
} catch (err) {
throw new AppError(err.message);
}
const session = await createDialogflowSession(999, projectName, jsonContent);
if (!session) {
throw new AppError("ERR_TEST_SESSION_DIALOG", 400);
}
let dialogFlowReply = await queryDialogFlow(
session,
projectName,
"TestSeesion",
"Ola",
language,
);
await session.close();
if (!dialogFlowReply) {
throw new AppError("ERR_TEST_REPLY_DIALOG", 400);
}
const messages = [];
for (let message of dialogFlowReply) {
messages.push(message.text.text[0]);
}
return { messages };
}
export default TestDialogflowSession;

View File

@ -0,0 +1,55 @@
import * as Yup from "yup";
import AppError from "../../errors/AppError";
import Dialogflow from "../../models/Dialogflow";
import ShowDialogflowService from "./ShowDialogflowService";
interface DialogflowData {
name?: string;
projectName?: string;
jsonContent?: string;
language?: string;
}
interface Request {
dialogflowData: DialogflowData;
dialogflowId: string;
}
const UpdateDialogflowService = async ({
dialogflowData,
dialogflowId
}: Request): Promise<Dialogflow> => {
const schema = Yup.object().shape({
name: Yup.string().min(2),
projectName: Yup.string().min(2),
jsonContent: Yup.string().min(2),
language: Yup.string().min(2)
});
const {
name,
projectName,
jsonContent,
language
} = dialogflowData;
try {
await schema.validate({ name, projectName, jsonContent, language });
} catch (err) {
throw new AppError(err.message);
}
const dialogflow = await ShowDialogflowService(dialogflowId);
await dialogflow.update({
name,
projectName,
jsonContent,
language
});
return dialogflow;
};
export default UpdateDialogflowService;

View File

@ -1,5 +1,6 @@
import * as Yup from "yup";
import AppError from "../../errors/AppError";
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
import Queue from "../../models/Queue";
interface QueueData {
@ -61,6 +62,8 @@ const CreateQueueService = async (queueData: QueueData): Promise<Queue> => {
const queue = await Queue.create(queueData);
deleteFileFromTMP(`botInfo.json`)
return queue;
};

View File

@ -5,6 +5,7 @@ import UserQueue from "../../models/UserQueue";
import ListTicketsServiceCache from "../TicketServices/ListTicketServiceCache";
import { deleteTicketsFieldsCache } from '../../helpers/TicketCache'
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
const DeleteQueueService = async (queueId: number | string): Promise<void> => {
@ -29,6 +30,8 @@ const DeleteQueueService = async (queueId: number | string): Promise<void> => {
}
await queue.destroy();
deleteFileFromTMP(`botInfo.json`)
};
export default DeleteQueueService;

View File

@ -1,6 +1,7 @@
import { Op } from "sequelize";
import * as Yup from "yup";
import AppError from "../../errors/AppError";
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
import Queue from "../../models/Queue";
import ShowQueueService from "./ShowQueueService";
@ -67,6 +68,8 @@ const UpdateQueueService = async (
await queue.update(queueData);
deleteFileFromTMP(`botInfo.json`)
return queue;
};

View File

@ -26,8 +26,8 @@ const FindOrCreateTicketService = async (
//Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue')
const botInfo = { isOnQueue: false }
const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false }

View File

@ -10,7 +10,7 @@ const ShowTicketService = async (id: string | number): Promise<Ticket> => {
{
model: Contact,
as: "contact",
attributes: ["id", "name", "number", "profilePicUrl"],
attributes: ["id", "name", "number", "profilePicUrl", "useDialogflow", "useQueues"],
include: ["extraInfo"]
},
{
@ -21,7 +21,8 @@ const ShowTicketService = async (id: string | number): Promise<Ticket> => {
{
model: Queue,
as: "queue",
attributes: ["id", "name", "color"]
attributes: ["id", "name", "color"],
include: ["dialogflow"]
}
]
});

View File

@ -4,6 +4,8 @@ import AppError from "../../errors/AppError";
import { SerializeUser } from "../../helpers/SerializeUser";
import User from "../../models/User";
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
interface Request {
email: string;
password: string;
@ -78,6 +80,8 @@ const CreateUserService = async ({
const serializedUser = SerializeUser(user);
deleteFileFromTMP(`botInfo.json`)
return serializedUser;
};

View File

@ -3,6 +3,7 @@ import AppError from "../../errors/AppError";
import Ticket from "../../models/Ticket";
import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus";
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
const DeleteUserService = async (id: string | number): Promise<void> => {
const user = await User.findOne({
@ -24,6 +25,8 @@ const DeleteUserService = async (id: string | number): Promise<void> => {
await user.destroy();
deleteFileFromTMP(`botInfo.json`)
};
export default DeleteUserService;

View File

@ -3,6 +3,8 @@ import * as Yup from "yup";
import AppError from "../../errors/AppError";
import ShowUserService from "./ShowUserService";
import deleteFileFromTMP from "../../helpers/deleteFileFromTMP";
interface UserData {
email?: string;
password?: string;
@ -67,6 +69,8 @@ const UpdateUserService = async ({
queues: user.queues
};
deleteFileFromTMP(`botInfo.json`)
return serializedUser;
};

View File

@ -0,0 +1,15 @@
const data:any[] = [
{
"id":"1",
"action":"transfer_to_attendant",
},
{
"id":"2",
"action":"request_endpoint",
},
]
export default data;

View File

@ -16,7 +16,9 @@ import {
Contact as WbotContact,
Message as WbotMessage,
MessageAck,
Client
Client,
Chat,
MessageMedia
} from "whatsapp-web.js";
import Contact from "../../models/Contact";
@ -35,7 +37,7 @@ import { date } from "faker";
import ShowQueueService from "../QueueService/ShowQueueService";
import ShowTicketMessage from "../TicketServices/ShowTicketMessage"
import BotIsOnQueue from "../../helpers/BotIsOnQueue"
import BotIsOnQueue from "../../helpers/BotIsOnQueue"
import Queue from "../../models/Queue";
import fs from 'fs';
@ -44,17 +46,23 @@ import { StartWhatsAppSession } from "../../services/WbotServices/StartWhatsAppS
import { removeWbot } from '../../libs/wbot'
import { restartWhatsSession } from "../../helpers/RestartWhatsSession";
// test del
import data_ura from './ura'
import msg_client_transfer from './ura_msg_transfer'
import final_message from "./ura_final_message";
import SendWhatsAppMessage from "./SendWhatsAppMessage";
import Whatsapp from "../../models/Whatsapp";
import { splitDateTime } from "../../helpers/SplitDateTime";
//
import { queryDialogFlow } from "../DialogflowServices/QueryDialogflow";
import { createDialogflowSessionWithModel } from "../DialogflowServices/CreateSessionDialogflow";
import bot_actions from './BotActions'
import ShowTicketService from "../TicketServices/ShowTicketService";
import { updateTicketCacheByTicketId } from '../../helpers/TicketCache'
import endPointQuery from "../../helpers/EndpointQuery";
interface Session extends Client {
@ -171,6 +179,171 @@ const verifyMessage = async (
};
async function sendDelayedMessages(wbot: Session, ticket: Ticket, contact: Contact, message: string) {
const body = message.replace(/\\n/g, '\n');
if (body.search('dialog_actions') != -1) {
let msgAction = botMsgActions(body)
console.log('gggggggggggggggggggggggggggggggggg msgAction: ', msgAction)
if (msgAction.actions[0] == 'request_endpoint') {
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, msgAction.msgBody);
await verifyMessage(sentMessage, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
// const url = 'https://sos.espacolaser.com.br/api/whatsapps/ticket/R32656'
const endPointResponse = await endPointQuery(msgAction.actions[1])
if (endPointResponse) {
const response = Object.entries(endPointResponse.data);
let msg_endpoint_response = ''
for (let i = 0; i < response.length; i++) {
msg_endpoint_response += `*${response[i][0]}*: ${response[i][1]}\n`
}
if (endPointResponse.data.status == 'EM ATENDIMENTO') {
// const msg = await wbot.sendMessage(`${contact.number}@c.us`, `Seu chamado está em atendimento pelo analista ${endPointResponse.data.tecnico} + Última informação do CHAT
// Verificar pelo “ID do solicitante” se existe chamado na HIT -> Se houver, informar status (Vamos alinhar os detalhes)`);
// const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n Seu chamado está em atendimento\n\n *Analista:* ${endPointResponse.data.tecnico}\n *Chat:* ${endPointResponse.data.chat ? endPointResponse.data.chat : ""}\n\n_Digite *0* para voltar ao menu principal._`);
const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n Seu chamado está em atendimento\n\n${msg_endpoint_response}\n_Digite *0* para voltar ao menu principal._`);
await verifyMessage(msg, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
}
else if (endPointResponse.data.categoria == 'ELOS' || (endPointResponse.data.subcategoria == 'VENDA' || endPointResponse.data.subcategoria == 'INDISPONIBILIDADE')) {
// const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n Vi que está com um problema no ELOS\n\n *Status:* ${endPointResponse.data.status}\n\nSe seu caso for urgente para concluir uma venda, digite “URGENTE”\n_Digite *0* para voltar ao menu principal._`);
const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n Vi que está com um problema no ELOS\n\n${msg_endpoint_response}\nSe seu caso for urgente para concluir uma venda, digite “URGENTE”\n_Digite *0* para voltar ao menu principal._`);
await verifyMessage(msg, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
}
else if ((endPointResponse.data.categoria == 'INFRAESTRUTURA' || endPointResponse.data.subcategoria == 'INTERNET' ||
endPointResponse.data.terceiro_nivel == 'QUEDA TOTAL' || endPointResponse.data.terceiro_nivel == 'PROBLEMA DE LENTIDÃO') ||
(endPointResponse.data.terceiro_nivel == 'PROBLEMA DE LENTIDÃO' || endPointResponse.data.terceiro_nivel == 'ABERTO')) {
const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n${msg_endpoint_response}\n Estamos direcionando seu atendimento para o Suporte. Em breve você será atendido por um de nossos atendentes!`);
await transferTicket(0, wbot, ticket, contact)
}
else {
// const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n *Status:* ${endPointResponse.data.status}\n *Data:* ${endPointResponse.data.data_chat ? endPointResponse.data.data_chat : ""}\n *Hora:* ${endPointResponse.data.hora_chat ? endPointResponse.data.hora_chat : ""} \n\n Por favor, aguarde atendimento e acompanhe sua solicitação no SOS.\n_Digite *0* para voltar ao menu principal._`);
const msg = await wbot.sendMessage(`${contact.number}@c.us`, `*Situação do chamado ${extractCallCode(msgAction.msgBody)}:*\n\n${msg_endpoint_response}\n Por favor, aguarde atendimento e acompanhe sua solicitação no SOS.\n_Digite *0* para voltar ao menu principal._`);
await verifyMessage(msg, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
}
}
else {
botSendMessage(ticket, contact, wbot, `Desculpe, nao foi possível realizar a consulta!\n _Digite *0* para voltar ao menu principal._`)
}
} else if (msgAction.actions[0] == 'queue_transfer') {
console.log('>>>>>>>>>>>>>>> msgAction: ', msgAction, ' | msgAction.actions[1]: ', msgAction.actions[1])
const msg = await wbot.sendMessage(`${contact.number}@c.us`, msgAction.msgBody);
await verifyMessage(msg, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
await transferTicket(+msgAction.actions[1], wbot, ticket, contact)
}
else if (msgAction.actions[0] == 'send_file'){
const sourcePath = path.join(__dirname,`../../../public/bot`)
const msg = await wbot.sendMessage(`${contact.number}@c.us`, msgAction.msgBody);
await verifyMessage(msg, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
await botSendMedia(ticket,contact,wbot,sourcePath, msgAction.actions[1])
}
}
else {
// const linesOfBody = body.split('\n');
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body);
await verifyMessage(sentMessage, ticket, contact);
await new Promise(f => setTimeout(f, 1000));
// for(let message of linesOfBody) {
// const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, message);
// await verifyMessage(sentMessage, ticket, contact);
// await new Promise(f => setTimeout(f, 1000));
// }
}
}
const extractCallCode = (str: string) => {
if (str.includes('*') && str.indexOf('*') < str.lastIndexOf('*')) {
return (str.substring(str.indexOf('*'), str.lastIndexOf('*') + 1)).split('*').join('')
}
return ''
}
const sendDialogflowAwswer = async (
wbot: Session,
ticket: Ticket,
msg: WbotMessage,
contact: Contact,
chat: Chat
) => {
const session = await createDialogflowSessionWithModel(ticket.queue.dialogflow);
if (session === undefined) {
return;
}
wbot.sendPresenceAvailable();
// console.log('typeof(msg.type): ', typeof (msg.type), ' | msg.type: ', msg.type)
if (msg.type != 'chat') {
botSendMessage(ticket, contact, wbot, `Desculpe, nao compreendi!\nEnvie apenas texto quando estiver interagindo com o bot!\n _Digite *0* para voltar ao menu principal._`)
return
}
if (msg.type == 'chat' && String(msg.body).length > 120) {
botSendMessage(ticket, contact, wbot, `Desculpe, nao compreendi!\nTexto acima de 120 caracteres!\n _Digite *0* para voltar ao menu principal._`)
return
}
let dialogFlowReply = await queryDialogFlow(
session,
ticket.queue.dialogflow.projectName,
msg.from,
msg.body,
ticket.queue.dialogflow.language
);
if (dialogFlowReply === null) {
return;
}
chat.sendStateTyping();
await new Promise(f => setTimeout(f, 1000));
for (let message of dialogFlowReply) {
await sendDelayedMessages(wbot, ticket, contact, message.text.text[0]);
}
}
const verifyQueue = async (
wbot: Session,
@ -195,8 +368,8 @@ const verifyQueue = async (
let choosenQueue = null
//Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue')
const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }
const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }
if (botInfo.isOnQueue) {
@ -247,7 +420,12 @@ const verifyQueue = async (
ticketId: ticket.id
});
data_ura.forEach((s, index) => { botOptions += `*${index + 1}* - ${s.option}\n` });
const _ticket = await ShowTicketService(ticket.id);
const chat = await msg.getChat();
await sendDialogflowAwswer(wbot, _ticket, msg, contact, chat);
return
}
//
@ -302,6 +480,27 @@ const verifyQueue = async (
}
};
const transferTicket = async (queueIndex: number, wbot: Session, ticket: Ticket, contact: Contact) => {
const botInfo = await BotIsOnQueue('botqueue')
const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId)
const queues = queuesWhatsGreetingMessage.queues
await botTransferTicket(queues[queueIndex], ticket, contact, wbot)
}
const botMsgActions = (params: string) => {
let lstActions = params.split('dialog_actions=')
let bodyMsg = lstActions[0].trim()
let actions = lstActions[1].split("=")
return { msgBody: bodyMsg, 'actions': [actions[0].trim(), actions[1].trim()] };
}
const isValidMsg = (msg: WbotMessage): boolean => {
if (msg.from === "status@broadcast") return false;
if (
@ -341,6 +540,37 @@ const botTransferTicket = async (queues: Queue, ticket: Ticket, contact: Contact
}
const botSendMedia = async (ticket: Ticket, contact: Contact, wbot: Session, mediaPath: string, fileNameExtension: string) => {
const debouncedSentMessage = debounce(
async () => {
// const sentMessage = await wbot.sendMessage(`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, newMedia, { sendAudioAsVoice: true });
const newMedia = MessageMedia.fromFilePath(`${mediaPath}/${fileNameExtension}`);
const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, newMedia, {caption: 'this is my caption'});
// client.sendMessage("xxxxxxxx@c.us", media, {caption: "some caption"}); })();
// client.sendMessage(msg.from, attachmentData, { caption: 'Here\'s your requested media.' });
await ticket.update({ lastMessage: fileNameExtension });
verifyMessage(sentMessage, ticket, contact);
},
3000,
ticket.id
);
debouncedSentMessage();
}
const botSendMessage = (ticket: Ticket, contact: Contact, wbot: Session, msg: string) => {
@ -469,308 +699,15 @@ const handleMessage = async (
// O bot interage com o cliente e encaminha o atendimento para fila de atendende quando o usuário escolhe a opção falar com atendente
//Habilitar esse caso queira usar o bot
// const botInfo = await BotIsOnQueue('botqueue')
const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }
const botInfo = await BotIsOnQueue('botqueue')
// const botInfo = { isOnQueue: false, botQueueId: 0, userIdBot: 0 }
if (botInfo.isOnQueue && !msg.fromMe && ticket.userId == botInfo.userIdBot) {
if (msg.body === '0') {
const queue = await ShowQueueService(ticket.queue.id);
const greetingMessage = `\u200e${queue.greetingMessage}`;
let options = "";
data_ura.forEach((s, index) => { options += `*${index + 1}* - ${s.option}\n` });
botSendMessage(ticket, contact, wbot, `${greetingMessage}\n\n${options}\n${final_message.msg}`)
}
else {
// Pega as ultimas 9 opções numericas digitadas pelo cliente em orde DESC
// Consulta apenas mensagens do usuári
let lastOption = ''
let ura_length = data_ura.length
let indexAttendant = data_ura.findIndex((u) => u.atendente)
let opt_user_attendant = '-1'
if (indexAttendant != -1) {
opt_user_attendant = data_ura[indexAttendant].id
}
//
let ticket_message = await ShowTicketMessage(ticket.id, true, ura_length, `^[0-${ura_length}}]$`);
if (ticket_message.length > 1) {
lastOption = ticket_message[1].body
const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId)
const queues = queuesWhatsGreetingMessage.queues
if (queues.length > 1) {
const index_opt_user_attendant = ticket_message.findIndex((q) => q.body == opt_user_attendant)
const index0 = ticket_message.findIndex((q) => q.body == '0')
if (index_opt_user_attendant != -1) {
if (index0 > -1 && index0 < index_opt_user_attendant) {
lastOption = ''
}
else {
lastOption = opt_user_attendant
}
}
}
}
//
//
// È numero
if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= data_ura.length)) {
const indexUra = data_ura.findIndex((ura) => ura.id == msg.body.trim())
if (indexUra != -1) {
if (data_ura[indexUra].id != opt_user_attendant && lastOption != opt_user_attendant) {
// test del
let next = true
let indexAux = ticket_message.findIndex((e) => e.body == '0')
let listMessage = null
if (indexAux != -1) {
listMessage = ticket_message.slice(0, indexAux)
}
else {
listMessage = ticket_message
}
let id = ''
let subUra = null
if (listMessage.length > 1) {
id = listMessage[listMessage.length - 1].body
subUra = data_ura.filter((e) => e.id == id)[0]
if (subUra && (!subUra.subOptions || subUra.subOptions.length == 0)) {
listMessage.pop()
}
}
if (listMessage.length > 1) {
id = listMessage[listMessage.length - 1].body
subUra = data_ura.filter((e) => e.id == id)[0]
if (subUra.subOptions && subUra.subOptions.length > 0) {
if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= subUra.subOptions?.length) && subUra.subOptions) {
if (subUra.subOptions[+msg.body - 1].responseToClient) {
botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\n${subUra.subOptions[+msg.body - 1].responseToClient}`)
}
else {
botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\n${subUra.subOptions[+msg.body - 1].subOpt}`)
}
const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId)
const queues = queuesWhatsGreetingMessage.queues
if (queues.length > 0) {
await botTransferTicket(queues[0], ticket, contact, wbot)
}
else {
console.log('NO QUEUE!')
}
}
else {
let options = "";
let subOptions: any[] = subUra.subOptions
subOptions?.forEach((s, index) => { options += `*${index + 1}* - ${s.subOpt}\n` });
botSendMessage(ticket, contact, wbot, `*${subUra.option}*\n\nDigite um número válido disponível no menu de opções de atendimento abaixo: \n${options}\n\n*0* - Voltar ao menu principal`)
}
next = false
}
}
//
if (next) {
if (data_ura[indexUra].subOptions && data_ura[indexUra].subOptions.length > 0) {
let options = "";
let option = data_ura[indexUra].option
let subOptions: any[] = data_ura[indexUra].subOptions
let description = data_ura[indexUra].description
subOptions?.forEach((s, index) => { options += `*${index + 1}* - ${s.subOpt}\n` });
const body = `\u200e${description}:\n${options}`
botSendMessage(ticket, contact, wbot, `*${option}*\n\n${body}\n\n *0* - Voltar ao menu principal`)
}
else {
//test del deletar isso (Usar somente na hit)
if (data_ura[indexUra].closeChat) {
const { ticket: res } = await UpdateTicketService({
ticketData: { 'status': 'closed', 'userId': botInfo.userIdBot }, ticketId: ticket.id
});
///////////////////////////////
const whatsapp = await ShowWhatsAppService(ticket.whatsappId);
const { farewellMessage } = whatsapp;
if (farewellMessage) {
await SendWhatsAppMessage({ body: farewellMessage, ticket: res });
}
///////////////////////////////
}
else {
botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`)
}
//
// botSendMessage(ticket, contact, wbot, `${data_ura[indexUra].description}\n\n *0* - Voltar ao menu principal`)
}
}
}
else if (data_ura[indexUra].id == opt_user_attendant) {
const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId)
const queues = queuesWhatsGreetingMessage.queues
// Se fila for maior que 1 exibi as opções fila para atendimento humano
if (queues.length > 1) {
let options = "";
queues.forEach((queue, index) => {
options += `*${index + 1}* - ${queue.name}\n`;
});
const body = `\u200eSelecione uma das opções de atendimento abaixo:\n${options}`;
botSendMessage(ticket, contact, wbot, body)
} // Para situações onde há apenas uma fila com exclusão da fila do bot, já direciona o cliente para essa fila de atendimento humano
else if (queues.length == 1) {
await botTransferTicket(queues[0], ticket, contact, wbot)
botSendMessage(ticket, contact, wbot, `${msg_client_transfer.msg}`)
}
}
else if (lastOption == opt_user_attendant) {
const queuesWhatsGreetingMessage = await queuesOutBot(wbot, botInfo.botQueueId)
const queues = queuesWhatsGreetingMessage.queues
// É numero
if (!Number.isNaN(Number(msg.body.trim())) && (+msg.body >= 0 && +msg.body <= queues.length)) {
await botTransferTicket(queues[+msg.body - 1], ticket, contact, wbot)
botSendMessage(ticket, contact, wbot, `${msg_client_transfer.msg}`)
}
else {
botSendMessage(ticket, contact, wbot, `Digite um número válido disponível no menu de opções de atendimento\n\n*0* - Voltar ao menu principal`)
}
}
}
}
else {
// É numero
if (!Number.isNaN(Number(msg.body.trim()))) {
botSendMessage(ticket, contact, wbot, `Opção numérica inválida!\nDigite um dos números mostrados no menu de opções\n\n*0* - Voltar ao menu principal`)
}
else {
botSendMessage(ticket, contact, wbot, `Digite um número válido disponível no menu de opções\n\n*0* - Voltar ao menu principal`)
}
}
}
await sendDialogflowAwswer(wbot, ticket, msg, contact, chat);
}
//
// test del
// if (msg.body.trim() == 'broken') {
// throw new Error('Throw makes it go boom!')
// }

View File

@ -73,6 +73,7 @@ const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
name: "",
number: "",
email: "",
useDialogflow: true,
};
const [contact, setContact] = useState(initialState);

View File

@ -0,0 +1,285 @@
import React, { useState, useEffect } from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import { toast } from "react-toastify";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
CircularProgress,
Select,
InputLabel,
MenuItem,
FormControl,
TextField,
} from '@material-ui/core'
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import toastError from "../../errors/toastError";
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap",
},
textField: {
marginRight: theme.spacing(1),
flex: 1,
},
btnWrapper: {
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
btnLeft: {
display: "flex",
marginRight: "auto",
marginLeft: 12,
},
formControl: {
margin: theme.spacing(1),
minWidth: 240,
},
colorAdorment: {
width: 20,
height: 20,
},
}));
const DialogflowSchema = Yup.object().shape({
name: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
projectName: Yup.string().min(3, "Too Short!").max(100, "Too Long!").required(),
jsonContent: Yup.string().min(3, "Too Short!").required(),
language: Yup.string().min(2, "Too Short!").max(50, "Too Long!").required(),
});
const DialogflowModal = ({ open, onClose, dialogflowId }) => {
const classes = useStyles();
const initialState = {
name: "",
projectName: "",
jsonContent: "",
language: "",
};
const [dialogflow, setDialogflow] = useState(initialState);
useEffect(() => {
(async () => {
if (!dialogflowId) return;
try {
const { data } = await api.get(`/dialogflow/${dialogflowId}`);
setDialogflow(prevState => {
return { ...prevState, ...data };
});
} catch (err) {
toastError(err);
}
})();
return () => {
setDialogflow({
name: "",
projectName: "",
jsonContent: "",
language: "",
});
};
}, [dialogflowId, open]);
const handleClose = () => {
onClose();
setDialogflow(initialState);
};
const handleTestSession = async (event, values) => {
try {
const {projectName, jsonContent, language } = values
await api.post(`/dialogflow/testSession`, {projectName, jsonContent, language });
toast.success( i18n.t("dialogflowModal.messages.testSuccess") );
} catch (err) {
toastError(err);
}
};
const handleSaveDialogflow = async values => {
try {
console.log(values)
if (dialogflowId) {
await api.put(`/dialogflow/${dialogflowId}`, values);
toast.success( i18n.t("dialogflowModal.messages.editSuccess") );
} else {
await api.post("/dialogflow", values);
toast.success( i18n.t("dialogflowModal.messages.addSuccess") );
}
handleClose();
} catch (err) {
toastError(err);
}
};
return (
<div className={classes.root}>
<Dialog open={open} onClose={handleClose} scroll="paper">
<DialogTitle>
{dialogflowId
? `${i18n.t("dialogflowModal.title.edit")}`
: `${i18n.t("dialogflowModal.title.add")}`}
</DialogTitle>
<Formik
initialValues={dialogflow}
enableReinitialize={true}
validationSchema={DialogflowSchema}
onSubmit={(values, actions, event) => {
setTimeout(() => {
console.log(actions);
handleSaveDialogflow(values);
actions.setSubmitting(false);
}, 400);
}}
>
{({ touched, errors, isSubmitting, values }) => (
<Form>
<DialogContent dividers>
<Field
as={TextField}
label={i18n.t("dialogflowModal.form.name")}
autoFocus
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<FormControl
variant="outlined"
className={classes.formControl}
margin="dense"
>
<InputLabel id="language-selection-input-label">
{i18n.t("dialogflowModal.form.language")}
</InputLabel>
<Field
as={Select}
label={i18n.t("dialogflowModal.form.language")}
name="language"
labelId="profile-selection-label"
error={touched.language && Boolean(errors.language)}
helperText={touched.language && errors.language}
id="language-selection"
required
>
<MenuItem value="pt-BR">Portugues</MenuItem>
<MenuItem value="en">Inglês</MenuItem>
<MenuItem value="es">Espanhol</MenuItem>
</Field>
</FormControl>
<div>
<Field
as={TextField}
label={i18n.t("dialogflowModal.form.projectName")}
name="projectName"
error={touched.projectName && Boolean(errors.projectName)}
helperText={touched.projectName && errors.projectName}
fullWidth
variant="outlined"
margin="dense"
/>
</div>
<div>
<Field
as={TextField}
label={i18n.t("dialogflowModal.form.jsonContent")}
type="jsonContent"
multiline
//inputRef={greetingRef}
rows={5}
fullWidth
name="jsonContent"
error={
touched.jsonContent && Boolean(errors.jsonContent)
}
helperText={
touched.jsonContent && errors.jsonContent
}
variant="outlined"
margin="dense"
/>
</div>
</DialogContent>
<DialogActions>
<Button
//type="submit"
onClick={(e) => handleTestSession(e, values)}
color="inherit"
disabled={isSubmitting}
name="testSession"
variant="outlined"
className={classes.btnLeft}
>
{i18n.t("dialogflowModal.buttons.test")}
</Button>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t("dialogflowModal.buttons.cancel")}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{dialogflowId
? `${i18n.t("dialogflowModal.buttons.okEdit")}`
: `${i18n.t("dialogflowModal.buttons.okAdd")}`}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
);
};
export default DialogflowModal;

View File

@ -13,14 +13,23 @@ import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle";
import CircularProgress from "@material-ui/core/CircularProgress";
import {
FormControl,
InputLabel,
Select,
MenuItem,
IconButton,
InputAdornment
} from "@material-ui/core"
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import toastError from "../../errors/toastError";
import ColorPicker from "../ColorPicker";
import { IconButton, InputAdornment } from "@material-ui/core";
import { Colorize } from "@material-ui/icons";
import useDialogflows from "../../hooks/useDialogflows";
const useStyles = makeStyles(theme => ({
root: {
@ -45,8 +54,8 @@ const useStyles = makeStyles(theme => ({
marginLeft: -12,
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
marginRight: theme.spacing(1),
minWidth: 200,
},
colorAdorment: {
width: 20,
@ -61,6 +70,7 @@ const QueueSchema = Yup.object().shape({
.required("Required"),
color: Yup.string().min(3, "Too Short!").max(9, "Too Long!").required(),
greetingMessage: Yup.string(),
dialogflowId: Yup.number(),
});
const QueueModal = ({ open, onClose, queueId }) => {
@ -70,10 +80,14 @@ const QueueModal = ({ open, onClose, queueId }) => {
name: "",
color: "",
greetingMessage: "",
dialogflowId: "",
};
const [colorPickerModalOpen, setColorPickerModalOpen] = useState(false);
const [queue, setQueue] = useState(initialState);
const [dialogflows, setDialogflows] = useState([]);
const { findAll: findAllDialogflows } = useDialogflows();
const greetingRef = useRef();
useEffect(() => {
@ -81,6 +95,10 @@ const QueueModal = ({ open, onClose, queueId }) => {
if (!queueId) return;
try {
const { data } = await api.get(`/queue/${queueId}`);
if(data.dialogflowId === null) {
data.dialogflowId = "";
}
setQueue(prevState => {
return { ...prevState, ...data };
});
@ -94,10 +112,20 @@ const QueueModal = ({ open, onClose, queueId }) => {
name: "",
color: "",
greetingMessage: "",
dialogflowId: "",
});
};
}, [queueId, open]);
useEffect(() => {
const loadDialogflows = async () => {
const list = await findAllDialogflows();
setDialogflows(list);
}
loadDialogflows();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleClose = () => {
onClose();
setQueue(initialState);
@ -105,6 +133,10 @@ const QueueModal = ({ open, onClose, queueId }) => {
const handleSaveQueue = async values => {
try {
if(values.dialogflowId === "") {
values.dialogflowId = null;
}
if (queueId) {
await api.put(`/queue/${queueId}`, values);
} else {
@ -213,6 +245,27 @@ const QueueModal = ({ open, onClose, queueId }) => {
margin="dense"
/>
</div>
<FormControl
variant="outlined"
className={classes.formControl}
margin="dense"
>
<InputLabel id="dialogflow-selection-input-label">
{i18n.t("queueModal.form.dialogflow")}
</InputLabel>
<Field
as={Select}
label={i18n.t("queueModal.form.dialogflow")}
name="dialogflowId"
labelId="dialogflow-selection-label"
id="dialogflow-selection"
>
<MenuItem value={''}>&nbsp;</MenuItem>
{dialogflows.map((dialogflow) => (
<MenuItem key={dialogflow.id} value={dialogflow.id}>{dialogflow.name}</MenuItem>
))}
</Field>
</FormControl>
</DialogContent>
<DialogActions>
<Button

View File

@ -34,6 +34,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
const history = useHistory();
const [anchorEl, setAnchorEl] = useState(null);
const [loading, setLoading] = useState(false);
const [useDialogflow, setUseDialogflow] = useState(ticket.contact.useDialogflow);
const ticketOptionsMenuOpen = Boolean(anchorEl);
const { user } = useContext(AuthContext);
@ -50,9 +51,9 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
const chatEndVal = (data) => {
if(data){
if (data) {
data = {...data, 'ticketId': ticket.id}
data = { ...data, 'ticketId': ticket.id }
@ -63,67 +64,77 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
}
const handleModal = (/*status, userId*/) => {
const handleModal = (/*status, userId*/) => {
render(<Modal
modal_header={'Finalização de Atendimento'}
func={chatEndVal}
statusChatEnd={statusChatEnd}
ticketId={ticket.id}
/>)
modal_header={'Finalização de Atendimento'}
func={chatEndVal}
statusChatEnd={statusChatEnd}
ticketId={ticket.id}
/>)
};
const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => {
const handleUpdateTicketStatus = async (e, status, userId, schedulingData = {}) => {
setLoading(true);
try {
setLoading(true);
try {
if(status==='closed'){
if (tabOption === 'search') {
setTabOption('open')
}
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
schedulingNotifyData: JSON.stringify(schedulingData)
});
}
else{
if (tabOption === 'search') {
setTabOption('open')
}
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null
});
if (status === 'closed') {
if (tabOption === 'search') {
setTabOption('open')
}
setLoading(false);
if (status === "open") {
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
schedulingNotifyData: JSON.stringify(schedulingData)
});
}
else {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
if (tabOption === 'search') {
setTabOption('open')
}
} catch (err) {
setLoading(false);
toastError(err);
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null
});
}
setLoading(false);
if (status === "open") {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
}
} catch (err) {
setLoading(false);
toastError(err);
}
};
const handleContactToggleUseDialogflow = async () => {
setLoading(true);
try {
const contact = await api.put(`/contacts/toggleUseDialogflow/${ticket.contact.id}`);
setUseDialogflow(contact.data.useDialogflow);
setLoading(false);
} catch (err) {
setLoading(false);
toastError(err);
}
};
return (
<div className={classes.actionButtons}>
{ticket.status === "closed" && (
@ -138,7 +149,7 @@ const TicketActionButtons = ({ ticket, statusChatEnd }) => {
)}
{ticket.status === "open" && (
<>
<ButtonWithSpinner style={{ marginRight: "70px" }}
<ButtonWithSpinner style={{ marginRight: "70px" }}
loading={loading}
startIcon={<Replay />}
size="small"

View File

@ -0,0 +1,16 @@
function getConfig(name, defaultValue=null) {
// If inside a docker container, use window.ENV
if( window.ENV !== undefined ) {
return window.ENV[name] || defaultValue;
}
return process.env[name] || defaultValue;
}
export function getBackendUrl() {
return getConfig('REACT_APP_BACKEND_URL');
}
export function getHoursCloseTicketsAuto() {
return getConfig('REACT_APP_HOURS_CLOSE_TICKETS_AUTO');
}

View File

@ -0,0 +1,12 @@
import api from "../../services/api";
const useDialogflows = () => {
const findAll = async () => {
const { data } = await api.get("/dialogflow");
return data;
}
return { findAll };
};
export default useDialogflows;

View File

@ -1,6 +1,7 @@
import React, { useContext, useEffect, useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import DeviceHubOutlined from "@material-ui/icons/DeviceHubOutlined"
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
@ -130,11 +131,18 @@ const MainListItems = (props) => {
role={user.profile}
perform="settings-view:show"
yes={() => (
<ListItemLink
to="/settings"
primary={i18n.t("mainDrawer.listItems.settings")}
icon={<SettingsOutlinedIcon />}
/>
<>
<ListItemLink
to="/settings"
primary={i18n.t("mainDrawer.listItems.settings")}
icon={<SettingsOutlinedIcon />}
/>
<ListItemLink
to="/Dialogflows"
primary={i18n.t("mainDrawer.listItems.dialogflow")}
icon={<DeviceHubOutlined />}
/>
</>
)}
/>
</>

View File

@ -0,0 +1,221 @@
import { format, parseISO } from "date-fns";
import React, { useReducer, useState } from "react";
import {
Button,
IconButton,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow
} from "@material-ui/core";
import { DeleteOutline, Edit } from "@material-ui/icons";
import { toast } from "react-toastify";
import ConfirmationModal from "../../components/ConfirmationModal";
import DialogflowModal from "../../components/DialogflowModal";
import MainContainer from "../../components/MainContainer";
import MainHeader from "../../components/MainHeader";
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
import TableRowSkeleton from "../../components/TableRowSkeleton";
import Title from "../../components/Title";
import toastError from "../../errors/toastError";
import api from "../../services/api";
import { i18n } from "../../translate/i18n";
import useLoadData from "../Queues/useLoadData.js";
import useSocket from "../Queues/useSocket";
const useStyles = makeStyles((theme) => ({
mainPaper: {
flex: 1,
padding: theme.spacing(1),
overflowY: "scroll",
...theme.scrollbarStyles,
},
customTableCell: {
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}));
const reducer = (state, action) => {
if (action.type === "LOAD_DIALOGFLOWS") {
const dialoflows = action.payload;
const newDialogflows = [];
dialoflows.forEach((dialogflow) => {
const dialogflowIndex = state.findIndex((q) => q.id === dialogflow.id);
if (dialogflowIndex !== -1) {
state[dialogflowIndex] = dialogflow;
} else {
newDialogflows.push(dialogflow);
}
});
return [...state, ...newDialogflows];
}
if (action.type === "UPDATE_DIALOGFLOWS") {
const dialogflow = action.payload;
const dialogflowIndex = state.findIndex((u) => u.id === dialogflow.id);
if (dialogflowIndex !== -1) {
state[dialogflowIndex] = dialogflow;
return [...state];
} else {
return [dialogflow, ...state];
}
}
if (action.type === "DELETE_DIALOGFLOW") {
const dialogflowId = action.payload;
const dialogflowIndex = state.findIndex((q) => q.id === dialogflowId);
if (dialogflowIndex !== -1) {
state.splice(dialogflowIndex, 1);
}
return [...state];
}
if (action.type === "RESET") {
return [];
}
};
const Dialogflows = () => {
const classes = useStyles();
const [dialogflows, dispatch] = useReducer(reducer, []);
const [loading, setLoading] = useState(false);
const [dialogflowModalOpen, setDialogflowModalOpen] = useState(false);
const [selectedDialogflow, setSelectedDialogflow] = useState(null);
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
useLoadData(setLoading, dispatch, '/dialogflow', 'LOAD_DIALOGFLOWS');
useSocket(dispatch, 'dialogflow', 'UPDATE_DIALOGFLOWS', 'DELETE_DIALOGFLOW', 'dialogflow', 'dialogflowId');
const handleOpenDialogflowModal = () => {
setDialogflowModalOpen(true);
setSelectedDialogflow(null);
};
const handleCloseDialogflowModal = () => {
setDialogflowModalOpen(false);
setSelectedDialogflow(null);
};
const handleEditDialogflow = (dialogflow) => {
setSelectedDialogflow(dialogflow);
setDialogflowModalOpen(true);
};
const handleCloseConfirmationModal = () => {
setConfirmModalOpen(false);
setSelectedDialogflow(null);
};
const handleDeleteDialogflow = async (dialogflowId) => {
try {
await api.delete(`/dialogflow/${dialogflowId}`);
toast.success(i18n.t("Dialogflow deleted successfully!"));
} catch (err) {
toastError(err);
}
setSelectedDialogflow(null);
};
return (
<MainContainer>
<ConfirmationModal
title={
selectedDialogflow &&
`${i18n.t("dialogflows.confirmationModal.deleteTitle")} ${
selectedDialogflow.name
}?`
}
open={confirmModalOpen}
onClose={handleCloseConfirmationModal}
onConfirm={() => handleDeleteDialogflow(selectedDialogflow.id)}
>
{i18n.t("dialogflows.confirmationModal.deleteMessage")}
</ConfirmationModal>
<DialogflowModal
open={dialogflowModalOpen}
onClose={handleCloseDialogflowModal}
dialogflowId={selectedDialogflow?.id}
/>
<MainHeader>
<Title>{i18n.t("dialogflows.title")}</Title>
<MainHeaderButtonsWrapper>
<Button
variant="contained"
color="primary"
onClick={handleOpenDialogflowModal}
>
{i18n.t("dialogflows.buttons.add")}
</Button>
</MainHeaderButtonsWrapper>
</MainHeader>
<Paper className={classes.mainPaper} variant="outlined">
<Table size="small">
<TableHead>
<TableRow>
<TableCell align="center">
{i18n.t("dialogflows.table.name")}
</TableCell>
<TableCell align="center">
{i18n.t("dialogflows.table.projectName")}
</TableCell>
<TableCell align="center">
{i18n.t("dialogflows.table.language")}
</TableCell>
<TableCell align="center">
{i18n.t("dialogflows.table.lastUpdate")}
</TableCell>
<TableCell align="center">
{i18n.t("dialogflows.table.actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<>
{dialogflows.map((dialogflow) => (
<TableRow key={dialogflow.id}>
<TableCell align="center">{dialogflow.name}</TableCell>
<TableCell align="center">{ dialogflow.projectName}</TableCell>
<TableCell align="center">{dialogflow.language}</TableCell>
<TableCell align="center">
{format(parseISO(dialogflow.updatedAt), "dd/MM/yy HH:mm")}
</TableCell>
<TableCell align="center">
<IconButton
size="small"
onClick={() => handleEditDialogflow(dialogflow)}
><Edit />
</IconButton>
<IconButton
size="small"
onClick={() => {
setSelectedDialogflow(dialogflow);
setConfirmModalOpen(true);
}}
>
<DeleteOutline />
</IconButton>
</TableCell>
</TableRow>
))}
{loading && <TableRowSkeleton columns={4} />}
</>
</TableBody>
</Table>
</Paper>
</MainContainer>
);
};
export default Dialogflows;

View File

@ -0,0 +1,22 @@
import { useEffect } from "react";
import toastError from "../../errors/toastError";
import api from "../../services/api";
const useLoadData = (setLoading, dispatch, route, dispatchType) => {
useEffect(() => {
(async () => {
setLoading(true);
try {
const { data } = await api.get(route);
dispatch({ type: dispatchType, payload: data });
setLoading(false);
} catch (err) {
toastError(err);
setLoading(false);
}
})();
}, []);
}
export default useLoadData;

View File

@ -0,0 +1,24 @@
import { useEffect } from "react";
import openSocket from "../../services/socket-io";
const useSocket = (dispatch, socketEvent, typeUpdate, typeDelete, payloadUpdate, payloadDelete) => {
useEffect(() => {
const socket = openSocket();
socket.on(socketEvent, (data) => {
if (data.action === "update" || data.action === "create") {
dispatch({ type: typeUpdate, payload: data[payloadUpdate] });
}
if (data.action === "delete") {
dispatch({ type: typeDelete, payload: data[payloadDelete] });
}
});
return () => {
socket.disconnect();
};
}, []);
}
export default useSocket;

View File

@ -21,7 +21,7 @@ import { AuthProvider } from "../context/Auth/AuthContext";
import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext";
import Route from "./Route";
import Dialogflows from "../pages/Dialogflow/";
const Routes = () => {
return (
@ -47,6 +47,7 @@ const Routes = () => {
<Route exact path="/quickAnswers" component={QuickAnswers} isPrivate />
<Route exact path="/Settings" component={Settings} isPrivate />
<Route exact path="/Queues" component={Queues} isPrivate />
<Route exact path="/Dialogflows" component={Dialogflows} isPrivate />
</LoggedInLayout>
</WhatsAppsProvider>
</Switch>

View File

@ -0,0 +1,8 @@
import openSocket from "socket.io-client";
import { getBackendUrl } from "../config";
function connectToSocket() {
return openSocket(getBackendUrl());
}
export default connectToSocket;

View File

@ -155,6 +155,7 @@ const messages = {
email: "Email",
extraName: "Field name",
extraValue: "Value",
dialogflow: "Dialogflow",
},
buttons: {
addExtraInfo: "Add information",
@ -188,6 +189,7 @@ const messages = {
form: {
name: "Name",
color: "Color",
dialogflow: "Dialogflow",
greetingMessage: "Greeting Message",
},
buttons: {
@ -196,6 +198,29 @@ const messages = {
cancel: "Cancel",
},
},
dialogflowModal: {
title: {
add: "Add Project",
edit: "Edit Project",
},
form: {
name: "Name",
projectName: "Project Name",
language: "Language",
jsonContent: "JsonContent"
},
buttons: {
okAdd: "Add",
okEdit: "Save",
cancel: "Cancel",
test: "Bot Test",
},
messages: {
testSuccess: "Dialogflow test successfully",
addSuccess: "Dialogflow added successfully",
editSuccess: "Dialogflow edited successfully",
}
},
userModal: {
title: {
add: "Add user",
@ -206,6 +231,7 @@ const messages = {
email: "Email",
password: "Password",
profile: "Profile",
whatsapp: "Default Connection",
},
buttons: {
okAdd: "Add",
@ -248,7 +274,9 @@ const messages = {
title: "Transfer Ticket",
fieldLabel: "Type to search for users",
fieldQueueLabel: "Transfer to queue",
fieldConnectionLabel: "Transfer to connection",
fieldQueuePlaceholder: "Please select a queue",
fieldConnectionPlaceholder: "Please select a connection",
noOptions: "No user found with this name",
buttons: {
ok: "Transfer",
@ -260,6 +288,7 @@ const messages = {
assignedHeader: "Working on",
noTicketsTitle: "Nothing here!",
noTicketsMessage: "No tickets found with this status or search term.",
connectionTitle: "Connection that is currently being used.",
buttons: {
accept: "Accept",
},
@ -284,6 +313,7 @@ const messages = {
administration: "Administration",
users: "Users",
settings: "Settings",
dialogflow: "Dialogflow",
},
appBar: {
user: {
@ -312,6 +342,24 @@ const messages = {
"Are you sure? It cannot be reverted! Tickets in this queue will still exist, but will not have any queues assigned.",
},
},
dialogflows: {
title: "Dialogflow",
table: {
name: "Name",
projectName: "Project Name",
lamguage: "Language",
lastUpdate:"Last Update",
actions: "Actions",
},
buttons: {
add: "Add Project",
},
confirmationModal: {
deleteTitle: "Delete",
deleteMessage:
"Are you sure? It cannot be reverted!",
},
},
queueSelect: {
inputLabel: "Queues",
},
@ -340,6 +388,7 @@ const messages = {
name: "Name",
email: "Email",
profile: "Profile",
whatsapp: "Default Connection",
actions: "Actions",
},
buttons: {
@ -371,6 +420,7 @@ const messages = {
header: {
assignedTo: "Assigned to:",
buttons: {
dialogflow: "Dialogflow",
return: "Return",
resolve: "Resolve",
reopen: "Reopen",
@ -392,6 +442,7 @@ const messages = {
},
ticketOptionsMenu: {
delete: "Delete",
useQueues: "¿Usar colas?",
transfer: "Transfer",
confirmationModal: {
title: "Delete ticket #",
@ -453,6 +504,9 @@ const messages = {
"This color is already in use, pick another one.",
ERR_WAPP_GREETING_REQUIRED:
"Greeting message is required if there is more than one queue.",
ERR_NO_DIALOG_FOUND: "No Dialogflow found with this ID.",
ERR_TEST_SESSION_DIALOG: "Error creating DialogFlow session",
ERR_TEST_REPLY_DIALOG: "Error testing DialogFlow configuration",
},
},
},

View File

@ -158,6 +158,7 @@ const messages = {
email: "Correo Electrónico",
extraName: "Nombre del Campo",
extraValue: "Valor",
dialogflow: "Dialogflow",
},
buttons: {
addExtraInfo: "Agregar información",
@ -191,6 +192,7 @@ const messages = {
form: {
name: "Nombre",
color: "Color",
dialogflow: "Dialogflow",
greetingMessage: "Mensaje de saludo",
},
buttons: {
@ -199,6 +201,29 @@ const messages = {
cancel: "Cancelar",
},
},
dialogflowModal: {
title: {
add: "Agregar cola",
edit: "Editar cola",
},
form: {
name: "Nombre",
projectName: "Nombre del proyecto",
language: "Idioma",
jsonContent: "JsonContent",
},
buttons: {
okAdd: "Añadir",
okEdit: "Ahorrar",
cancel: "Cancelar",
test: "Testar Bot",
},
messages: {
testSuccess: "Prueba Dialogflow con éxito",
addSuccess: "Dialogflow agregado con éxito",
editSuccess: "Dialogflow editado con éxito",
}
},
userModal: {
title: {
add: "Agregar usuario",
@ -209,6 +234,7 @@ const messages = {
email: "Correo Electrónico",
password: "Contraseña",
profile: "Perfil",
whatsapp: "Conexión estándar",
},
buttons: {
okAdd: "Agregar",
@ -251,7 +277,9 @@ const messages = {
title: "Transferir Ticket",
fieldLabel: "Escriba para buscar usuarios",
fieldQueueLabel: "Transferir a la cola",
fieldConnectionLabel: "Transferir to conexión",
fieldQueuePlaceholder: "Seleccione una cola",
fieldConnectionPlaceholder: "Seleccione una conexión",
noOptions: "No se encontraron usuarios con ese nombre",
buttons: {
ok: "Transferir",
@ -262,6 +290,7 @@ const messages = {
pendingHeader: "Cola",
assignedHeader: "Trabajando en",
noTicketsTitle: "¡Nada acá!",
connectionTitle: "Conexión que se está utilizando actualmente.",
noTicketsMessage:
"No se encontraron tickets con este estado o término de búsqueda",
buttons: {
@ -280,6 +309,7 @@ const messages = {
mainDrawer: {
listItems: {
dashboard: "Dashboard",
connections: "Conexiones",
tickets: "Tickets",
contacts: "Contactos",
@ -288,6 +318,7 @@ const messages = {
administration: "Administración",
users: "Usuarios",
settings: "Configuración",
dialogflow: "Dialogflow",
},
appBar: {
user: {
@ -316,6 +347,24 @@ const messages = {
"¿Estás seguro? ¡Esta acción no se puede revertir! Los tickets en esa cola seguirán existiendo, pero ya no tendrán ninguna cola asignada.",
},
},
dialoflows: {
title: "Dialogflow",
table: {
name: "Nombre",
projectName: "Nombre del proyecto",
language: "Idioma",
lastUpdate: "Última Actualización",
actions: "Comportamiento",
},
buttons: {
add: "Agregar proyecto",
},
confirmationModal: {
deleteTitle: "Eliminar",
deleteMessage:
"¿Estás seguro? ¡Esta acción no se puede revertir!",
},
},
queueSelect: {
inputLabel: "Linhas",
},
@ -345,6 +394,7 @@ const messages = {
name: "Nombre",
email: "Correo Electrónico",
profile: "Perfil",
whatsapp: "Conexión estándar",
actions: "Acciones",
},
buttons: {
@ -376,6 +426,7 @@ const messages = {
header: {
assignedTo: "Asignado a:",
buttons: {
dialogflow: "Dialogflow",
return: "Devolver",
resolve: "Resolver",
reopen: "Reabrir",
@ -398,6 +449,7 @@ const messages = {
},
ticketOptionsMenu: {
delete: "Borrar",
useQueues: "Use Queues",
transfer: "Transferir",
confirmationModal: {
title: "¿Borrar ticket #",
@ -460,6 +512,9 @@ const messages = {
"Este color ya está en uso, elija otro.",
ERR_WAPP_GREETING_REQUIRED:
"El mensaje de saludo es obligatorio cuando hay más de una cola.",
ERR_NO_DIALOG_FOUND: "No se encontró Dialogflow con este ID.",
ERR_TEST_SESSION_DIALOG: "Error al crear la sesión de dialogflow",
ERR_TEST_REPLY_DIALOG: "Error al probar la configuración de DialogFlow",
},
},
},

View File

@ -80,8 +80,7 @@ const messages = {
qrcode: {
title: "Esperando leitura do QR Code",
content:
// "Clique no botão 'QR CODE' e leia o QR Code com o seu celular para iniciar a sessão",
"Entre em contato com o suporte para realizar a leitura do QR code",
"Clique no botão 'QR CODE' e leia o QR Code com o seu celular para iniciar a sessão",
},
connected: {
title: "Conexão estabelecida!",
@ -158,6 +157,7 @@ const messages = {
email: "Email",
extraName: "Nome do campo",
extraValue: "Valor",
dialogflow: "Dialogflow",
},
buttons: {
addExtraInfo: "Adicionar informação",
@ -191,6 +191,7 @@ const messages = {
form: {
name: "Nome",
color: "Cor",
dialogflow: "Dialogflow",
greetingMessage: "Mensagem de saudação",
},
buttons: {
@ -199,6 +200,29 @@ const messages = {
cancel: "Cancelar",
},
},
dialogflowModal: {
title: {
add: "Adicionar projeto",
edit: "Editar projeto",
},
form: {
name: "Nome",
projectName: "Nome do Projeto",
language: "Linguagem",
jsonContent: "JsonContent",
},
buttons: {
okAdd: "Adicionar",
okEdit: "Salvar",
cancel: "Cancelar",
test: "Testar Bot",
},
messages: {
testSuccess: "Dialogflow testado com sucesso!",
addSuccess: "Dialogflow adicionado com sucesso.",
editSuccess: "Dialogflow editado com sucesso.",
}
},
userModal: {
title: {
add: "Adicionar usuário",
@ -209,6 +233,7 @@ const messages = {
email: "Email",
password: "Senha",
profile: "Perfil",
whatsapp: "Conexão Padrão",
},
buttons: {
okAdd: "Adicionar",
@ -241,7 +266,7 @@ const messages = {
search: { title: "Busca" },
},
search: {
placeholder: "Busca telefone/nome",
placeholder: "Buscar tickets e mensagens",
},
buttons: {
showAll: "Todos",
@ -251,7 +276,9 @@ const messages = {
title: "Transferir Ticket",
fieldLabel: "Digite para buscar usuários",
fieldQueueLabel: "Transferir para fila",
fieldConnectionLabel: "Transferir para conexão",
fieldQueuePlaceholder: "Selecione uma fila",
fieldConnectionPlaceholder: "Selecione uma conexão",
noOptions: "Nenhum usuário encontrado com esse nome",
buttons: {
ok: "Transferir",
@ -264,6 +291,7 @@ const messages = {
noTicketsTitle: "Nada aqui!",
noTicketsMessage:
"Nenhum ticket encontrado com esse status ou termo pesquisado",
connectionTitle: "Conexão que está sendo utilizada atualmente.",
buttons: {
accept: "Aceitar",
},
@ -288,6 +316,7 @@ const messages = {
administration: "Administração",
users: "Usuários",
settings: "Configurações",
dialogflow: "Dialogflow",
},
appBar: {
user: {
@ -316,6 +345,24 @@ const messages = {
"Você tem certeza? Essa ação não pode ser revertida! Os tickets dessa fila continuarão existindo, mas não terão mais nenhuma fila atribuída.",
},
},
dialogflows: {
title: "Dialogflow",
table: {
name: "Nome",
projectName: "Nome do Projeto",
language: "Linguagem",
lastUpdate: "Ultima atualização",
actions: "Ações",
},
buttons: {
add: "Adicionar Projeto",
},
confirmationModal: {
deleteTitle: "Excluir",
deleteMessage:
"Você tem certeza? Essa ação não pode ser revertida! e será removida das filas e conexões vinculadas",
},
},
queueSelect: {
inputLabel: "Filas",
},
@ -345,6 +392,7 @@ const messages = {
name: "Nome",
email: "Email",
profile: "Perfil",
whatsapp: "Conexão Padrão",
actions: "Ações",
},
buttons: {
@ -376,6 +424,7 @@ const messages = {
header: {
assignedTo: "Atribuído à:",
buttons: {
dialogflow: "Dialogflow",
return: "Retornar",
resolve: "Resolver",
reopen: "Reabrir",
@ -398,6 +447,7 @@ const messages = {
},
ticketOptionsMenu: {
delete: "Deletar",
useQueues: "Usar fila?",
transfer: "Transferir",
confirmationModal: {
title: "Deletar o ticket do contato",
@ -458,6 +508,9 @@ const messages = {
"Esta cor já está em uso, escolha outra.",
ERR_WAPP_GREETING_REQUIRED:
"A mensagem de saudação é obrigatório quando há mais de uma fila.",
ERR_NO_DIALOG_FOUND: "Nenhuma Dialogflow encontrado com este ID",
ERR_TEST_SESSION_DIALOG: "Erro ao criar sessão do DialogFlow",
ERR_TEST_REPLY_DIALOG: "Erro ao testar configuração do DialogFlow",
},
},
},