Skip to content

Commit

Permalink
Integração com ChatGPT
Browse files Browse the repository at this point in the history
Comandos:
/gpt - para receber a resposta em texto
/gptM - pra receber a resposta em imagem
  • Loading branch information
rtenorioh committed May 4, 2023
1 parent c5a842d commit 6b5d228
Show file tree
Hide file tree
Showing 17 changed files with 468 additions and 6 deletions.
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"multer": "^1.4.2",
"mustache": "^4.2.0",
"mysql2": "^2.2.5",
"openai": "^3.2.1",
"pg": "^8.4.1",
"pino": "^8.11.0",
"pino-pretty": "^10.0.0",
Expand Down
41 changes: 41 additions & 0 deletions backend/src/controllers/IntegrationController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Request, Response } from "express";

import { getIO } from "../libs/socket";
import AppError from "../errors/AppError";

import UpdateIntegrationService from "../services/IntegrationServices/UpdateIntegrationService";
import ListIntegrationsService from "../services/IntegrationServices/ListIntegrationsService";

export const index = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile === "") {
throw new AppError("ERR_NO_PERMISSION", 403);
}

const integrations = await ListIntegrationsService();

return res.status(200).json(integrations);
};

export const update = async (
req: Request,
res: Response
): Promise<Response> => {
if (req.user.profile !== "admin") {
throw new AppError("ERR_NO_PERMISSION", 403);
}
const { integrationKey: key } = req.params;
const { value } = req.body;

const integration = await UpdateIntegrationService({
key,
value
});

const io = getIO();
io.emit("integrations", {
action: "update",
integration
});

return res.status(200).json(integration);
};
4 changes: 3 additions & 1 deletion backend/src/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import UserQueue from "../models/UserQueue";
import QuickAnswer from "../models/QuickAnswer";
import Tag from "../models/Tag";
import ContactTag from "../models/ContactTag";
import Integration from "../models/Integration";

// eslint-disable-next-line
const dbConfig = require("../config/database");
Expand All @@ -32,7 +33,8 @@ const models = [
UserQueue,
QuickAnswer,
Tag,
ContactTag
ContactTag,
Integration
];

sequelize.addModels(models);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { QueryInterface, DataTypes } from "sequelize";

module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.createTable("Integrations", {
key: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
value: {
type: DataTypes.TEXT,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
allowNull: false
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false
}
});
},

down: (queryInterface: QueryInterface) => {
return queryInterface.dropTable("Integrations");
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { QueryInterface } from "sequelize";

module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Integrations",
[
{
key: "organization",
value: "",
createdAt: new Date(),
updatedAt: new Date()
},
{
key: "apikey",
value: "",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},

down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Integrations", {});
}
};
108 changes: 106 additions & 2 deletions backend/src/libs/wbot.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,95 @@
import qrCode from "qrcode-terminal";
import { Client, LocalAuth } from "whatsapp-web.js";
import { Client, LocalAuth, MessageMedia } from "whatsapp-web.js";
import { Configuration, CreateImageRequestSizeEnum, OpenAIApi } from "openai";
import { getIO } from "./socket";
import Whatsapp from "../models/Whatsapp";
import AppError from "../errors/AppError";
import { logger } from "../utils/logger";
import { handleMessage } from "../services/WbotServices/wbotMessageListener";
import Integration from "../models/Integration";

interface Session extends Client {
id?: number;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface CreateImageRequest {
prompt: string;
n?: number;
size?: CreateImageRequestSizeEnum;
}

async function findIntegrationValue(key: string): Promise<string | null> {
// Encontre a instância de integração com base na chave fornecida
const integration = await Integration.findOne({
where: { key }
});

// Se a instância for encontrada, retorne o valor
if (integration) {
return integration.value;
}

// Caso contrário, retorne null
return null as string | null;
}

let openai: OpenAIApi;

(async () => {
const organizationDB: string | null = await findIntegrationValue(
"organization"
);
const apiKeyDB: string | null = await findIntegrationValue("apikey");

const configuration = new Configuration({
organization: organizationDB ?? "",
apiKey: apiKeyDB ?? ""
});

openai = new OpenAIApi(configuration);
})();

// gera resposta em texto
const getDavinciResponse = async (clientText: string): Promise<string> => {
const options = {
model: "text-davinci-003", // Modelo GPT a ser usado
prompt: clientText, // Texto enviado pelo usuário
temperature: 1, // Nível de variação das respostas geradas, 1 é o máximo
max_tokens: 4000 // Quantidade de tokens (palavras) a serem retornadas pelo bot, 4000 é o máximo
};

try {
const response = await openai.createCompletion(options);
let botResponse = "";
response.data.choices.forEach(({ text }) => {
botResponse += text;
});
return `Chat GPT 🤖\n\n ${botResponse.trim()}`;
} catch (e) {
return `❌ OpenAI Response Error: ${e.response.data.error.message}`;
}
};

// gera a url da imagem
const getDalleResponse = async (
clientText: string
): Promise<string | undefined> => {
const options: CreateImageRequest = {
prompt: clientText, // Descrição da imagem
n: 1, // Número de imagens a serem geradas
// eslint-disable-next-line no-underscore-dangle
size: CreateImageRequestSizeEnum._1024x1024 // Tamanho da imagem
};

try {
const response = await openai.createImage(options);
return response.data.data[0].url;
} catch (e) {
return `❌ OpenAI Response Error: ${e.response.data.error.message}`;
}
};

const sessions: Session[] = [];

const syncUnreadMessages = async (wbot: Session) => {
Expand All @@ -33,7 +113,7 @@ const syncUnreadMessages = async (wbot: Session) => {
};

export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {
return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
try {
logger.level = "trace";
const io = getIO();
Expand Down Expand Up @@ -158,6 +238,30 @@ export const initWbot = async (whatsapp: Whatsapp): Promise<Session> => {

resolve(wbot);
});

wbot.on("message", async msg => {
const msgChatGPT: string = msg.body;
// mensagem de texto
if (msgChatGPT.includes("/gpt ")) {
const index = msgChatGPT.indexOf(" ");
const question = msgChatGPT.substring(index + 1);
getDavinciResponse(question).then((response: string) => {
wbot.sendMessage(msg.from, response);
});
}
// imagem
if (msgChatGPT.includes("/gptM ")) {
const index = msgChatGPT.indexOf(" ");
const imgDescription = msgChatGPT.substring(index + 1);
const imgUrl = await getDalleResponse(imgDescription);
if (imgUrl) {
const media = await MessageMedia.fromUrl(imgUrl);
wbot.sendMessage(msg.from, media, { caption: imgDescription });
} else {
wbot.sendMessage(msg.from, "❌ Não foi possível gerar a imagem.");
}
}
});
} catch (err: any) {
logger.error(err);
}
Expand Down
26 changes: 26 additions & 0 deletions backend/src/models/Integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
Table,
Column,
CreatedAt,
UpdatedAt,
Model,
PrimaryKey
} from "sequelize-typescript";

@Table
class Integration extends Model<Integration> {
@PrimaryKey
@Column
key: string;

@Column
value: string;

@CreatedAt
createdAt: Date;

@UpdatedAt
updatedAt: Date;
}

export default Integration;
2 changes: 2 additions & 0 deletions backend/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import queueRoutes from "./queueRoutes";
import quickAnswerRoutes from "./quickAnswerRoutes";
import apiRoutes from "./apiRoutes";
import tagRoutes from "./tagRoutes";
import integrationRoutes from "./integrationRoutes";

const routes = Router();

Expand All @@ -27,5 +28,6 @@ routes.use(queueRoutes);
routes.use(quickAnswerRoutes);
routes.use("/api/messages", apiRoutes);
routes.use(tagRoutes);
routes.use(integrationRoutes);

export default routes;
16 changes: 16 additions & 0 deletions backend/src/routes/integrationRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Router } from "express";
import isAuth from "../middleware/isAuth";

import * as IntegrationController from "../controllers/IntegrationController";

const integrationRoutes = Router();

integrationRoutes.get("/integrations", isAuth, IntegrationController.index);

integrationRoutes.put(
"/integrations/:integrationKey",
isAuth,
IntegrationController.update
);

export default integrationRoutes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Integration from "../../models/Integration";

const ListIntegrationsService = async (): Promise<
Integration[] | undefined
> => {
const integrations = await Integration.findAll();

return integrations;
};

export default ListIntegrationsService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AppError from "../../errors/AppError";
import Integration from "../../models/Integration";

interface Request {
key: string;
value: string;
}

const UpdateIntegrationService = async ({
key,
value
}: Request): Promise<Integration | undefined> => {
const integration = await Integration.findOne({
where: { key }
});

if (!integration) {
throw new AppError("ERR_NO_INTEGRATION_FOUND", 404);
}

await integration.update({ value });

return integration;
};

export default UpdateIntegrationService;
6 changes: 6 additions & 0 deletions frontend/src/layout/MainListItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Code,
ContactPhoneOutlined,
DashboardOutlined,
DeveloperModeOutlined,
LocalOffer,
MenuBook,
PeopleAltOutlined,
Expand Down Expand Up @@ -152,6 +153,11 @@ const MainListItems = (props) => {
primary={i18n.t("mainDrawer.listItems.queues")}
icon={<AccountTreeOutlined />}
/>
<ListItemLink
to="/Integrations"
primary={i18n.t("mainDrawer.listItems.integrations")}
icon={<DeveloperModeOutlined />}
/>
<ListItemLink
to="/settings"
primary={i18n.t("mainDrawer.listItems.settings")}
Expand Down
Loading

0 comments on commit 6b5d228

Please sign in to comment.