From ca34f34eba91205d9261aae5f0a8f7da2904c848 Mon Sep 17 00:00:00 2001 From: CodePhix Date: Wed, 6 Dec 2023 15:53:32 -0300 Subject: [PATCH] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20com=20o=20Chat=20GPT?= =?UTF-8?q?=20e=20inbox=20Chatwoot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Realizei a implementação com o ChatGPT e com o id do inbox do chatwoot --- dev-env | 5 + src/config.ts | 14 + src/config/env.config.ts | 15 + src/dev-env.yml | 43 +-- src/docs/swagger.yaml | 2 +- src/libs/amqp.server.ts | 2 +- src/libs/db.connect.ts | 3 +- src/libs/openai.ts | 34 +++ src/libs/redis.client.ts | 29 +- src/libs/redis.ts | 9 + src/libs/sqs.server.ts | 2 +- src/main.ts | 2 +- src/prompts/contabilAgent.ts | 49 ++++ src/prompts/pizzaAgent.ts | 94 ++++++ src/utils/initPrompt.ts | 13 + src/utils/use-multi-file-auth-state-db.ts | 7 +- src/validate/validate.schema.ts | 45 +++ .../controllers/chatwoot.controller.ts | 2 + .../controllers/instance.controller.ts | 104 ++++++- src/whatsapp/controllers/openai.controller.ts | 85 ++++++ src/whatsapp/controllers/sqs.controller.ts | 4 +- src/whatsapp/dto/chatwoot.dto.ts | 1 + src/whatsapp/dto/contactopenai.dto.ts | 5 + src/whatsapp/dto/instance.dto.ts | 8 + src/whatsapp/dto/openai.dto.ts | 6 + src/whatsapp/dto/sqs.dto.ts | 2 +- src/whatsapp/guards/instance.guard.ts | 4 +- src/whatsapp/models/chatwoot.model.ts | 2 + src/whatsapp/models/contactOpenai.model.ts | 20 ++ src/whatsapp/models/index.ts | 2 + src/whatsapp/models/openai.model.ts | 22 ++ src/whatsapp/models/sqs.model.ts | 2 +- src/whatsapp/repository/openai.repository.ts | 153 ++++++++++ src/whatsapp/repository/repository.manager.ts | 18 +- src/whatsapp/repository/sqs.repository.ts | 2 +- src/whatsapp/routers/index.router.ts | 9 +- src/whatsapp/routers/instance.router.ts | 26 +- src/whatsapp/routers/openai.router.ts | 86 ++++++ src/whatsapp/routers/sqs.router.ts | 2 +- src/whatsapp/services/._whatsapp.service.ts | Bin 0 -> 4096 bytes src/whatsapp/services/chatwoot.service.ts | 149 +++++++--- src/whatsapp/services/monitor.service.ts | 19 +- src/whatsapp/services/openai.service.ts | 57 ++++ src/whatsapp/services/sqs.service.ts | 4 +- src/whatsapp/services/typebot.service.ts | 259 ++++++++++++++++- src/whatsapp/services/whatsapp.service.ts | 272 +++++++++++++++++- src/whatsapp/types/wa.types.ts | 6 + src/whatsapp/whatsapp.module.ts | 15 +- 48 files changed, 1599 insertions(+), 115 deletions(-) create mode 100644 dev-env create mode 100644 src/config.ts create mode 100644 src/libs/openai.ts create mode 100644 src/libs/redis.ts create mode 100644 src/prompts/contabilAgent.ts create mode 100644 src/prompts/pizzaAgent.ts create mode 100644 src/utils/initPrompt.ts create mode 100644 src/whatsapp/controllers/openai.controller.ts create mode 100644 src/whatsapp/dto/contactopenai.dto.ts create mode 100644 src/whatsapp/dto/openai.dto.ts create mode 100644 src/whatsapp/models/contactOpenai.model.ts create mode 100644 src/whatsapp/models/openai.model.ts create mode 100644 src/whatsapp/repository/openai.repository.ts create mode 100644 src/whatsapp/routers/openai.router.ts create mode 100644 src/whatsapp/services/._whatsapp.service.ts create mode 100644 src/whatsapp/services/openai.service.ts diff --git a/dev-env b/dev-env new file mode 100644 index 00000000..9a93b693 --- /dev/null +++ b/dev-env @@ -0,0 +1,5 @@ +# OPENAI_API_KEY="" +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 +REDIS_PASS=0 diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..86a45b42 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,14 @@ +import dotenv from "dotenv" + +dotenv.config() + +export const config = { + openAI: { + apiToken: process.env.OPENAI_API_KEY, + }, + redis: { + host: process.env.REDIS_HOST || "localhost", + port: (process.env.REDIS_PORT as unknown as number) || 6379, + db: (process.env.REDIS_DB as unknown as number) || 0, + }, +} diff --git a/src/config/env.config.ts b/src/config/env.config.ts index dcb90fbc..306a4032 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -48,6 +48,7 @@ export type CleanStoreConf = { export type DBConnection = { URI: string; DB_PREFIX_NAME: string; + DB_PREFIX_FINAL_NAME: string; }; export type Database = { CONNECTION: DBConnection; @@ -74,6 +75,12 @@ export type Sqs = { REGION: string; }; +export type Openai = { + CHAVE: string; + ENABLED: boolean; + URI: string; +}; + export type Websocket = { ENABLED: boolean; }; @@ -145,6 +152,7 @@ export interface Env { REDIS: Redis; RABBITMQ: Rabbitmq; SQS: Sqs; + OPENAI: Openai; WEBSOCKET: Websocket; LOG: Log; DEL_INSTANCE: DelInstance; @@ -218,6 +226,7 @@ export class ConfigService { CONNECTION: { URI: process.env.DATABASE_CONNECTION_URI || '', DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME || 'evolution', + DB_PREFIX_FINAL_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_FINAL_NAME || '-api', }, ENABLED: process.env?.DATABASE_ENABLED === 'true', SAVE_DATA: { @@ -244,6 +253,12 @@ export class ConfigService { ACCOUNT_ID: process.env.SQS_ACCOUNT_ID || '', REGION: process.env.SQS_REGION || '', }, + + OPENAI: { + CHAVE: process.env?.OPENAI_ENABLED || '', + ENABLED: process.env?.OPENAI_ENABLED === 'true', + URI: process.env.OPENAI_URI || '', + }, WEBSOCKET: { ENABLED: process.env?.WEBSOCKET_ENABLED === 'true', }, diff --git a/src/dev-env.yml b/src/dev-env.yml index b2a2e521..bb57f26c 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -7,8 +7,8 @@ # Choose the server type for the application SERVER: TYPE: http # https - PORT: 8080 # 443 - URL: localhost + PORT: 3333 # 443 + URL: 127.0.0.1 CORS: ORIGIN: @@ -48,8 +48,8 @@ DEL_INSTANCE: false # or false # Temporary data storage STORE: - MESSAGES: true - MESSAGE_UP: true + MESSAGES: false + MESSAGE_UP: false CONTACTS: true CHATS: true @@ -62,17 +62,20 @@ CLEAN_STORE: # Permanent data storage DATABASE: - ENABLED: false + ENABLED: true CONNECTION: URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true" - DB_PREFIX_NAME: evolution + + DB_PREFIX_NAME: whatsapp + DB_PREFIX_FINAL_NAME: "-api" + DB_PREFIX_FINAL_NAME_VENOM: "-venom" # Choose the data you want to save in the application's database or store SAVE_DATA: - INSTANCE: false + INSTANCE: true NEW_MESSAGE: false MESSAGE_UPDATE: false - CONTACTS: false - CHATS: false + CONTACTS: true + CHATS: true REDIS: ENABLED: false @@ -83,22 +86,31 @@ RABBITMQ: ENABLED: false URI: "amqp://guest:guest@localhost:5672" +OPENAI: + CHAVE: "" + ENABLED: false + PROMPTS: false + URI: "" + SQS: - ENABLED: true + ENABLED: false ACCESS_KEY_ID: "" SECRET_ACCESS_KEY: "" ACCOUNT_ID: "" REGION: "us-east-1" - + WEBSOCKET: ENABLED: false +TYPEBOT: + API_VERSION: 'v1' # v1 | v2 + # Global Webhook Settings # Each instance's Webhook URL and events will be requested at the time it is created WEBHOOK: # Define a global webhook that will listen for enabled events from all instances GLOBAL: - URL: + URL: "" ENABLED: false # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event WEBHOOK_BY_EVENTS: false @@ -134,11 +146,11 @@ WEBHOOK: CHAMA_AI_ACTION: false # This event is used to send errors to the webhook ERRORS: false - ERRORS_WEBHOOK: + ERRORS_WEBHOOK: "" CONFIG_SESSION_PHONE: # Name that will be displayed on smartphone connection - CLIENT: "Evolution API" + CLIENT: "Chat API" NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari # Set qrcode display limit @@ -146,9 +158,6 @@ QRCODE: LIMIT: 30 COLOR: "#198754" -TYPEBOT: - API_VERSION: 'v1' # v1 | v2 - # Defines an authentication type for the api # We recommend using the apikey because it will allow you to use a custom token, # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 589ba8dc..5f9235ac 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -25,7 +25,7 @@ info: [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442) - version: 1.5.5 + version: 1.5.4 contact: name: DavidsonGomes email: contato@agenciadgcode.com diff --git a/src/libs/amqp.server.ts b/src/libs/amqp.server.ts index fc95b33c..758c49ed 100644 --- a/src/libs/amqp.server.ts +++ b/src/libs/amqp.server.ts @@ -1,6 +1,6 @@ import * as amqp from 'amqplib/callback_api'; -import { configService, Rabbitmq } from '../config/env.config'; +import { configService, Rabbitmq, Openai } from '../config/env.config'; import { Logger } from '../config/logger.config'; const logger = new Logger('AMQP'); diff --git a/src/libs/db.connect.ts b/src/libs/db.connect.ts index b11610c7..815d04ce 100644 --- a/src/libs/db.connect.ts +++ b/src/libs/db.connect.ts @@ -10,7 +10,8 @@ export const dbserver = (() => { if (db.ENABLED) { logger.verbose('connecting'); const dbs = mongoose.createConnection(db.CONNECTION.URI, { - dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api', + + dbName: db.CONNECTION.DB_PREFIX_NAME + db.CONNECTION.DB_PREFIX_FINAL_NAME + '-config', }); logger.verbose('connected in ' + db.CONNECTION.URI); logger.info('ON - dbName: ' + dbs['$dbName']); diff --git a/src/libs/openai.ts b/src/libs/openai.ts new file mode 100644 index 00000000..7f3dba41 --- /dev/null +++ b/src/libs/openai.ts @@ -0,0 +1,34 @@ +import { Configuration, OpenAIApi } from "openai" + +import { config } from "../config" + +const configuration = new Configuration({ + apiKey: config.openAI.apiToken, +}) + +export const openai = new OpenAIApi(configuration) + + +export class OpenAIService { + constructor( + private readonly apikey: String, + ) { + } + + private WaOpenai: OpenAIApi; + + public SetOpenai() { + + const configuration = new Configuration({ + apiKey: config.openAI.apiToken, + }) + + this.WaOpenai = new OpenAIApi(configuration) + } + + public openai() { + + return this.WaOpenai; + } + +} diff --git a/src/libs/redis.client.ts b/src/libs/redis.client.ts index 1d74ff15..a1ed4017 100644 --- a/src/libs/redis.client.ts +++ b/src/libs/redis.client.ts @@ -5,11 +5,6 @@ import { Redis } from '../config/env.config'; import { Logger } from '../config/logger.config'; export class RedisCache { - private readonly logger = new Logger(RedisCache.name); - private client: RedisClientType; - private statusConnection = false; - private instanceName: string; - private redisEnv: Redis; constructor() { this.logger.verbose('RedisCache instance created'); @@ -19,6 +14,21 @@ export class RedisCache { }); } + // constructor() { + // this.logger.verbose('instance created'); + // process.on('beforeExit', async () => { + // this.logger.verbose('instance destroyed'); + // if (this.statusConnection) { + // this.logger.verbose('instance disconnect'); + // await this.client.disconnect(); + // } + // }); + // } + + private statusConnection = false; + private instanceName: string; + private redisEnv: Redis; + public set reference(reference: string) { this.logger.verbose('set reference: ' + reference); this.instanceName = reference; @@ -27,14 +37,18 @@ export class RedisCache { public async connect(redisEnv: Redis) { this.logger.verbose('Connecting to Redis...'); this.client = createClient({ url: redisEnv.URI }); + //this.logger.verbose('connected in ' + redisEnv.URI); this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err)); - await this.client.connect(); this.statusConnection = true; this.redisEnv = redisEnv; this.logger.verbose(`Connected to ${redisEnv.URI}`); } + private readonly logger = new Logger(RedisCache.name); + private client: RedisClientType; + + public async disconnect() { if (this.statusConnection) { await this.client.disconnect(); @@ -46,11 +60,14 @@ export class RedisCache { public async instanceKeys(): Promise { const keys: string[] = []; try { + //this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*'); + //return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']); this.logger.verbose('Fetching instance keys'); for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) { keys.push(key); } } catch (error) { + this.logger.error(error); this.logger.error('Error fetching instance keys ' + error); } return keys; diff --git a/src/libs/redis.ts b/src/libs/redis.ts new file mode 100644 index 00000000..154d1dee --- /dev/null +++ b/src/libs/redis.ts @@ -0,0 +1,9 @@ +import { Redis } from "ioredis" + +import { config } from "../config" + +export const redis = new Redis({ + host: config.redis.host, + port: config.redis.port, + db: config.redis.db, +}) diff --git a/src/libs/sqs.server.ts b/src/libs/sqs.server.ts index 04184542..014c6bc2 100644 --- a/src/libs/sqs.server.ts +++ b/src/libs/sqs.server.ts @@ -94,4 +94,4 @@ export const removeQueues = (instanceName: string, events: string[]) => { }, ); }); -}; +}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 52bdd798..46615c61 100644 --- a/src/main.ts +++ b/src/main.ts @@ -128,7 +128,7 @@ function bootstrap() { initIO(server); if (configService.get('RABBITMQ')?.ENABLED) initAMQP(); - + if (configService.get('SQS')?.ENABLED) initSQS(); onUnexpectedError(); diff --git a/src/prompts/contabilAgent.ts b/src/prompts/contabilAgent.ts new file mode 100644 index 00000000..4603a55c --- /dev/null +++ b/src/prompts/contabilAgent.ts @@ -0,0 +1,49 @@ +export const promptContabil = `Você é uma assistente virtual de atendimento de um escritorio de contabilidade chamado {{ storeName }}. Você deve ser educada, atenciosa, amigável, cordial e muito paciente. + + + +O roteiro de atendimento é: + +1. Saudação inicial: Cumprimente o cliente e agradeça por entrar em contato. +2. Coleta de informações: Solicite ao cliente seu nome para registro caso ainda não tenha registrado. Informe que os dados são apenas para controle de atendimento e não serão compartilhados com terceiros. +3. Pergunte o setor que deseja seguir, sendo o seguinte Financeiro, suporte ou novo cliente. + +4. Serviços de Contabilidade: +4.1 Apresente os principais serviços de contabilidade oferecidos pelo escritório. +4.2 Oferecemos uma ampla gama de serviços contábeis, incluindo ABERTURA DE EMPRESA, ABERTURA DE FILIAL, ASSESSORIA CONTÁBIL, ASSESSORIA FISCAL, BAIXA E REGULARIZAÇÃO DE EMPRESAS, CONSULTORIA CONTÁBIL, PLANEJAMENTO TRIBUTÁRIO, RECURSOS HUMANOS, REVISÃO TRIBUTÁRIA, Administração de Condomínios. Como posso ajudá-lo com seus desafios financeiros hoje? +5. Perguntas Frequentes (FAQ): +5.1 Forneça respostas para perguntas frequentes sobre impostos, contabilidade e serviços específicos. +5.2 Aqui estão algumas perguntas frequentes sobre nossos serviços: [ + Pergunta 1: Vou precisar falar com meu antigo contador ou escritório de contabilidade sobre a migração? + Resposta 1: Não. Fique tranquilo pois a Anexo Gestão Contábil cuida disso para você. Você só precisará enviar o seu Certificado Digital. Caso não tenha um e-CNPJ(A1), vamos te ajudar no processo de migração de outra forma, falando com o contador ou auxiliando na contratação de um Certificado Digital. Depois dessa etapa nós vamos fazer todo o seu processo com o seu antigo contador ou escritório de contabilidade. É simples, transparente e sem burocracia para você. + + Pergunta 2: Quanto tempo demora para mudar para a Anexo Gestão Contábil? + Resposta 2: O processo é rápido, prático e você não precisa sair de casa. Aproximadamente 5 dias úteis é o prazo após conseguirmos acessar a documentação via Certificado Digital ou com o contador antigo. +]. + +5. Agendamento de Consulta: +5.2 Permita que os usuários agendem uma consulta com um contador. +5.3 Pergunte sobre a data e hora preferidas. +5.3 Se você gostaria de agendar uma consulta com um de nossos contadores, por favor, informe-nos sobre a data e horário que funcionam melhor para você. + +6. Impostos: +6.1 Forneça informações sobre prazos de declaração de impostos, documentos necessários e dicas fiscais. +6.2 Os prazos para a declaração de impostos estão se aproximando. Aqui estão algumas dicas sobre como se preparar e os documentos necessários. + +7. Contato e Localização: +7.1 Fornecer informações de contato, incluindo número de telefone, endereço de e-mail e endereço físico do escritório. +7.2 Você pode nos contatar pelo telefone [número], enviar um e-mail para [e-mail] ou nos visitar no seguinte endereço [endereço]. + +8. Encaminhamento para um Contador: +8.1 Se o chatbot não puder responder a uma pergunta específica, ofereça a opção de ser encaminhado para um contador real. +8.2 Se você tiver uma pergunta mais complexa que eu não possa responder, gostaria de ser encaminhado para um dos nossos contadores?" + +9. Despedida: +9.1 Encerre a conversa de maneira cortês e ofereça informações de contato adicionais. +9.2 Obrigado por usar nossos serviços! Se precisar de assistência futura, estamos à disposição. Tenha um ótimo dia!" + +10. Feedback: +10.1 Solicite feedback aos usuários para melhorar o desempenho do chatbot. +10.2 Gostaríamos de ouvir sua opinião! Em uma escala de 1 a 5, quão útil você achou nosso chatbot hoje? + +` diff --git a/src/prompts/pizzaAgent.ts b/src/prompts/pizzaAgent.ts new file mode 100644 index 00000000..eeea4ab0 --- /dev/null +++ b/src/prompts/pizzaAgent.ts @@ -0,0 +1,94 @@ +export const promptPizza = `Você é uma assistente virtual de atendimento de uma pizzaria chamada {{ storeName }}. Você deve ser educada, atenciosa, amigável, cordial e muito paciente. + +Você não pode oferecer nenhum item ou sabor que não esteja em nosso cardápio. Siga estritamente as listas de opções. + +O código do pedido é: {{ orderCode }} + +O roteiro de atendimento é: + +1. Saudação inicial: Cumprimente o cliente e agradeça por entrar em contato. +2. Coleta de informações: Solicite ao cliente seu nome para registro caso ainda não tenha registrado. Informe que os dados são apenas para controle de pedidos e não serão compartilhados com terceiros. +3. Quantidade de pizzas: Pergunte ao cliente quantas pizzas ele deseja pedir. +4. Sabores: Envie a lista resumida apenas com os nomes de sabores salgados e doces e pergunte ao cliente quais sabores de pizza ele deseja pedir. +4.1 O cliente pode escolher a pizza fracionada em até 2 sabores na mesma pizza. +4.2 Se o cliente escolher mais de uma pizza, pergunte se ele deseja que os sabores sejam repetidos ou diferentes. +4.3 Se o cliente escolher sabores diferentes, pergunte quais são os sabores de cada pizza. +4.4 Se o cliente escolher sabores repetidos, pergunte quantas pizzas de cada sabor ele deseja. +4.5 Se o cliente estiver indeciso, ofereça sugestões de sabores ou se deseja receber o cardápio completo. +4.6 Se o sabor não estiver no cardápio, não deve prosseguir com o atendimento. Nesse caso informe que o sabor não está disponível e agradeça o cliente. +5. Tamanho: Pergunte ao cliente qual o tamanho das pizzas. +5.1 Se o cliente escolher mais de um tamanho, pergunte se ele deseja que os tamanhos sejam repetidos ou diferentes. +5.2 Se o cliente escolher tamanhos diferentes, pergunte qual o tamanho de cada pizza. +5.3 Se o cliente escolher tamanhos repetidos, pergunte quantas pizzas de cada tamanho ele deseja. +5.4 Se o cliente estiver indeciso, ofereça sugestões de tamanhos. Se for para 1 pessoa o tamanho pequeno é ideal, para 2 pessoas o tamanho médio é ideal e para 3 ou mais pessoas o tamanho grande é ideal. +6. Ingredientes adicionais: Pergunte ao cliente se ele deseja adicionar algum ingrediente extra. +6.1 Se o cliente escolher ingredientes extras, pergunte quais são os ingredientes adicionais de cada pizza. +6.2 Se o cliente estiver indeciso, ofereça sugestões de ingredientes extras. +7. Remover ingredientes: Pergunte ao cliente se ele deseja remover algum ingrediente, por exemplo, cebola. +7.1 Se o cliente escolher ingredientes para remover, pergunte quais são os ingredientes que ele deseja remover de cada pizza. +7.2 Não é possível remover ingredientes que não existam no cardápio. +8. Borda: Pergunte ao cliente se ele deseja borda recheada. +8.1 Se o cliente escolher borda recheada, pergunte qual o sabor da borda recheada. +8.2 Se o cliente estiver indeciso, ofereça sugestões de sabores de borda recheada. Uma dica é oferecer a borda como sobremesa com sabor de chocolate. +9. Bebidas: Pergunte ao cliente se ele deseja pedir alguma bebida. +9.1 Se o cliente escolher bebidas, pergunte quais são as bebidas que ele deseja pedir. +9.2 Se o cliente estiver indeciso, ofereça sugestões de bebidas. +10. Entrega: Pergunte ao cliente se ele deseja receber o pedido em casa ou se prefere retirar no balcão. +10.1 Se o cliente escolher entrega, pergunte qual o endereço de entrega. O endereço deverá conter Rua, Número, Bairro e CEP. +10.2 Os CEPs de 12.220-000 até 12.330-000 possuem uma taxa de entrega de R$ 10,00. +10.3 Se o cliente escolher retirar no balcão, informe o endereço da pizzaria e o horário de funcionamento: Rua Abaeté, 123, Centro, São José dos Campos, SP. Horário de funcionamento: 18h às 23h. +11. Forma de pagamento: Pergunte ao cliente qual a forma de pagamento desejada, oferecendo opções como dinheiro, PIX, cartão de crédito ou débito na entrega. +11.1 Se o cliente escolher dinheiro, pergunte o valor em mãos e calcule o troco. O valor informado não pode ser menor que o valor total do pedido. +11.2 Se o cliente escolher PIX, forneça a chave PIX CNPJ: 1234 +11.3 Se o cliente escolher cartão de crédito/débito, informe que a máquininha será levada pelo entregador. +12. Mais alguma coisa? Pergunte ao cliente se ele deseja pedir mais alguma coisa. +12.1 Se o cliente desejar pedir mais alguma coisa, pergunte o que ele deseja pedir. +12.2 Se o cliente não desejar pedir mais nada, informe o resumo do pedido: Dados do cliente, quantidade de pizzas, sabores, tamanhos, ingredientes adicionais, ingredientes removidos, borda, bebidas, endereço de entrega, forma de pagamento e valor total. +12.3 Confirmação do pedido: Pergunte ao cliente se o pedido está correto. +12.4 Se o cliente confirmar o pedido, informe o tempo de entrega médio de 45 minutos e agradeça. +12.5 Se o cliente não confirmar o pedido, pergunte o que está errado e corrija o pedido. +13. Despedida: Agradeça o cliente por entrar em contato. É muito importante que se despeça informando o número do pedido. + +Cardápio de pizzas salgadas (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Muzzarella: Queijo mussarela, tomate e orégano. R$ 25,00 / R$ 30,00 / R$ 35,00 +- Calabresa: Calabresa, cebola e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Nordestina: Carne de sol, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Frango: Frango desfiado, milho e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Frango c/ Catupiry: Frango desfiado, catupiry e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- A moda da Casa: Carne de sol, bacon, cebola e orégano. R$ 40,00 / R$ 45,00 / R$ 50,00 +- Presunto: Presunto, queijo mussarela e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Quatro Estações: Presunto, queijo mussarela, ervilha, milho, palmito e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Mista: Presunto, queijo mussarela, calabresa, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Toscana: Calabresa, bacon, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Portuguesa: Presunto, queijo mussarela, calabresa, ovo, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Dois Queijos: Queijo mussarela, catupiry e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Quatro Queijos: Queijo mussarela, provolone, catupiry, parmesão e orégano. R$ 40,00 / R$ 45,00 / R$ 50,00 +- Salame: Salame, queijo mussarela e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Atum: Atum, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 + +Cardápio de pizzas doces (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Chocolate: Chocolate ao leite e granulado. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Romeu e Julieta: Goiabada e queijo mussarela. R$ 30,00 / R$ 35,00 / R$ 40,00 +- California: Banana, canela e açúcar. R$ 30,00 / R$ 35,00 / R$ 40,00 + +Extras/Adicionais (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Catupiry: R$ 5,00 / R$ 7,00 / R$ 9,00 + +Bordas (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Chocolate: R$ 5,00 / R$ 7,00 / R$ 9,00 +- Cheddar: R$ 5,00 / R$ 7,00 / R$ 9,00 +- Catupiry: R$ 5,00 / R$ 7,00 / R$ 9,00 + +Bebidas: + +- Coca-Cola 2L: R$ 10,00 +- Coca-Cola Lata: R$ 8,00 +- Guaraná 2L: R$ 10,00 +- Guaraná Lata: R$ 7,00 +- Água com Gás 500 ml: R$ 5,00 +- Água sem Gás 500 ml: R$ 4,00 +` diff --git a/src/utils/initPrompt.ts b/src/utils/initPrompt.ts new file mode 100644 index 00000000..6512a050 --- /dev/null +++ b/src/utils/initPrompt.ts @@ -0,0 +1,13 @@ +import { promptPizza } from "../prompts/pizzaAgent" + +export function initPrompt(storeName: string, orderCode: string, prompt?: string ): string { + if(prompt){ + return prompt + .replace(/{{[\s]?storeName[\s]?}}/g, storeName) + .replace(/{{[\s]?orderCode[\s]?}}/g, orderCode) + }else{ + return promptPizza + .replace(/{{[\s]?storeName[\s]?}}/g, storeName) + .replace(/{{[\s]?orderCode[\s]?}}/g, orderCode) + } +} diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index 237b15d0..fc6c3e8f 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -19,7 +19,10 @@ export async function useMultiFileAuthStateDb( const client = dbserver.getClient(); const collection = client - .db(configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') + .db( + configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + + configService.get('DATABASE').CONNECTION.DB_PREFIX_FINAL_NAME + ) .collection(coll); const writeData = async (data: any, key: string): Promise => { @@ -33,6 +36,7 @@ export async function useMultiFileAuthStateDb( }; } return await collection.replaceOne({ _id: key }, msgParsed, { + //return await collection.replaceOne({ _id: key }, JSON.parse(JSON.stringify(data, BufferJSON.replacer)), { upsert: true, }); } catch (error) { @@ -47,6 +51,7 @@ export async function useMultiFileAuthStateDb( if (data?.content_array) { data = data.content_array; } + //const data = await collection.findOne({ _id: key }); const creds = JSON.stringify(data); return JSON.parse(creds, BufferJSON.reviver); } catch (error) { diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 9781e18c..e8c9d3fd 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -987,6 +987,51 @@ export const rabbitmqSchema: JSONSchema7 = { ...isNotEmpty('enabled'), }; +export const openaiSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chave: { type: 'string' }, + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +}; + + export const sqsSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts index 46b93aee..1998f9ec 100644 --- a/src/whatsapp/controllers/chatwoot.controller.ts +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -72,6 +72,7 @@ export class ChatwootController { token: '', sign_msg: false, name_inbox: '', + id_inbox: '', webhook_url: '', }; } @@ -86,6 +87,7 @@ export class ChatwootController { public async receiveWebhook(instance: InstanceDto, data: any) { logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance'); + const chatwootService = new ChatwootService(waMonitor, this.configService); return chatwootService.receiveWebhook(instance, data); diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 4c4e5cfb..f1660e33 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -13,6 +13,7 @@ import { ChatwootService } from '../services/chatwoot.service'; import { WAMonitoringService } from '../services/monitor.service'; import { ProxyService } from '../services/proxy.service'; import { RabbitmqService } from '../services/rabbitmq.service'; +import { OpenaiService } from '../services/openai.service'; import { SettingsService } from '../services/settings.service'; import { SqsService } from '../services/sqs.service'; import { TypebotService } from '../services/typebot.service'; @@ -33,6 +34,7 @@ export class InstanceController { private readonly settingsService: SettingsService, private readonly websocketService: WebsocketService, private readonly rabbitmqService: RabbitmqService, + private readonly openaiService: OpenaiService, private readonly proxyService: ProxyService, private readonly sqsService: SqsService, private readonly typebotService: TypebotService, @@ -66,8 +68,14 @@ export class InstanceController { websocket_events, rabbitmq_enabled, rabbitmq_events, + + openai_chave, + openai_enabled, + openai_events, + sqs_enabled, sqs_events, + typebot_url, typebot, typebot_expire, @@ -81,6 +89,7 @@ export class InstanceController { this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); this.logger.verbose('checking duplicate token'); + await this.authService.checkDuplicateToken(token); this.logger.verbose('creating instance'); @@ -250,6 +259,56 @@ export class InstanceController { } } + let openaiEvents: string[]; + + if (openai_enabled) { + this.logger.verbose('creating openai'); + try { + let newChave: string = ""; + let newEvents: string[] = []; + if (openai_events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = openai_events; + } + this.openaiService.create(instance, { + chave: newChave, + enabled: true, + events: newEvents, + }); + + openaiEvents = (await this.openaiService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + if (proxy) { this.logger.verbose('creating proxy'); try { @@ -266,6 +325,7 @@ export class InstanceController { } } + let sqsEvents: string[]; if (sqs_enabled) { @@ -380,6 +440,11 @@ export class InstanceController { enabled: rabbitmq_enabled, events: rabbitmqEvents, }, + openai: { + chave: openai_chave, + enabled: openai_enabled, + events: openaiEvents, + }, sqs: { enabled: sqs_enabled, events: sqsEvents, @@ -396,7 +461,6 @@ export class InstanceController { }, settings, qrcode: getQrcode, - proxy, }; this.logger.verbose('instance created'); @@ -479,6 +543,11 @@ export class InstanceController { enabled: rabbitmq_enabled, events: rabbitmqEvents, }, + openai: { + chave: openai_chave, + enabled: openai_enabled, + events: openaiEvents, + }, sqs: { enabled: sqs_enabled, events: sqsEvents, @@ -506,7 +575,6 @@ export class InstanceController { name_inbox: instance.instanceName, webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, }, - proxy, }; } catch (error) { this.logger.error(error.message[0]); @@ -514,6 +582,23 @@ export class InstanceController { } } + + public async qrInstance({ instanceName }: InstanceDto) { + try { + this.logger.verbose('requested qrInstance from ' + instanceName + ' instance'); + + this.logger.verbose('logging out instance: ' + instanceName); + ///this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); + + const qrcode = await this.waMonitor.waInstances[instanceName]?.instance.qrcode; + return qrcode.base64; + + } catch (error) { + this.logger.error(error); + return ''; + } + } + public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { try { this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance'); @@ -628,18 +713,21 @@ export class InstanceController { } try { this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues(); + this.waMonitor.waInstances[instanceName]?.removeOpenaiQueues(); if (instance.state === 'connecting') { this.logger.verbose('logging out instance: ' + instanceName); await this.logout({ instanceName }); + delete this.waMonitor.waInstances[instanceName]; + return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; + } else { + this.logger.verbose('deleting instance: ' + instanceName); + + delete this.waMonitor.waInstances[instanceName]; + this.eventEmitter.emit('remove.instance', instanceName, 'inner'); + return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; } - - this.logger.verbose('deleting instance: ' + instanceName); - - delete this.waMonitor.waInstances[instanceName]; - this.eventEmitter.emit('remove.instance', instanceName, 'inner'); - return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; } catch (error) { throw new BadRequestException(error.toString()); } diff --git a/src/whatsapp/controllers/openai.controller.ts b/src/whatsapp/controllers/openai.controller.ts new file mode 100644 index 00000000..45eaea90 --- /dev/null +++ b/src/whatsapp/controllers/openai.controller.ts @@ -0,0 +1,85 @@ +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { OpenaiService } from '../services/openai.service'; + +const logger = new Logger('OpenaiController'); + +export class OpenaiController { + constructor(private readonly openaiService: OpenaiService) {} + + public async createOpenai(instance: InstanceDto, data: OpenaiDto) { + logger.verbose('requested createOpenai from ' + instance.instanceName + ' instance'); + + if (!data.chave) { + logger.verbose('openai sem chave'); + data.chave = ''; + } + + if (!data.enabled) { + logger.verbose('openai disabled'); + data.events = []; + } + + if (data.events?.length === 0) { + logger.verbose('openai events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.openaiService.create(instance, data); + } + + public async findOpenai(instance: InstanceDto) { + logger.verbose('requested findOpenai from ' + instance.instanceName + ' instance'); + return this.openaiService.find(instance); + } + + public async createContactOpenai(instance: InstanceDto, data: ContactOpenaiDto) { + logger.verbose('requested createOpenai from ' + instance.instanceName + ' instance'); + + if (!data.contact) { + logger.verbose('openai sem chave'); + data.contact = ''; + } + + if (!data.enabled) { + logger.verbose('openai disabled'); + data.enabled = false; + } + + data.owner = instance.instanceName; + + return this.openaiService.createContact(instance, data); + } + + public async findContactOpenai(instance: InstanceDto) { + logger.verbose('requested findOpenai from ' + instance.instanceName + ' instance'); + return this.openaiService.findContact(instance); + } +} diff --git a/src/whatsapp/controllers/sqs.controller.ts b/src/whatsapp/controllers/sqs.controller.ts index 063e29ed..ea5c84ba 100644 --- a/src/whatsapp/controllers/sqs.controller.ts +++ b/src/whatsapp/controllers/sqs.controller.ts @@ -6,7 +6,7 @@ import { SqsService } from '../services/sqs.service'; const logger = new Logger('SqsController'); export class SqsController { - constructor(private readonly sqsService: SqsService) {} + constructor(private readonly sqsService: SqsService) { } public async createSqs(instance: InstanceDto, data: SqsDto) { logger.verbose('requested createSqs from ' + instance.instanceName + ' instance'); @@ -53,4 +53,4 @@ export class SqsController { logger.verbose('requested findSqs from ' + instance.instanceName + ' instance'); return this.sqsService.find(instance); } -} +} \ No newline at end of file diff --git a/src/whatsapp/dto/chatwoot.dto.ts b/src/whatsapp/dto/chatwoot.dto.ts index b270c869..5617dadb 100644 --- a/src/whatsapp/dto/chatwoot.dto.ts +++ b/src/whatsapp/dto/chatwoot.dto.ts @@ -4,6 +4,7 @@ export class ChatwootDto { token?: string; url?: string; name_inbox?: string; + id_inbox?: string; sign_msg?: boolean; number?: string; reopen_conversation?: boolean; diff --git a/src/whatsapp/dto/contactopenai.dto.ts b/src/whatsapp/dto/contactopenai.dto.ts new file mode 100644 index 00000000..af1eee64 --- /dev/null +++ b/src/whatsapp/dto/contactopenai.dto.ts @@ -0,0 +1,5 @@ +export class ContactOpenaiDto { + contact?: string; + enabled: boolean; + owner: string; +} diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts index c63620c5..0ccd9e9f 100644 --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -23,8 +23,15 @@ export class InstanceDto { websocket_events?: string[]; rabbitmq_enabled?: boolean; rabbitmq_events?: string[]; + + openai_chave?: boolean; + openai_prompts?: string; + openai_enabled?: boolean; + openai_events?: string[]; + sqs_enabled?: boolean; sqs_events?: string[]; + typebot_url?: string; typebot?: string; typebot_expire?: number; @@ -32,5 +39,6 @@ export class InstanceDto { typebot_delay_message?: number; typebot_unknown_message?: string; typebot_listening_from_me?: boolean; + proxy_enabled?: boolean; proxy?: string; } diff --git a/src/whatsapp/dto/openai.dto.ts b/src/whatsapp/dto/openai.dto.ts new file mode 100644 index 00000000..fba34fdf --- /dev/null +++ b/src/whatsapp/dto/openai.dto.ts @@ -0,0 +1,6 @@ +export class OpenaiDto { + chave?: string; + enabled: boolean; + prompts?: string; + events?: string[]; +} diff --git a/src/whatsapp/dto/sqs.dto.ts b/src/whatsapp/dto/sqs.dto.ts index 9b8aeedd..23cbbd22 100644 --- a/src/whatsapp/dto/sqs.dto.ts +++ b/src/whatsapp/dto/sqs.dto.ts @@ -1,4 +1,4 @@ export class SqsDto { enabled: boolean; events?: string[]; -} +} \ No newline at end of file diff --git a/src/whatsapp/guards/instance.guard.ts b/src/whatsapp/guards/instance.guard.ts index 6b193411..65fdafb7 100644 --- a/src/whatsapp/guards/instance.guard.ts +++ b/src/whatsapp/guards/instance.guard.ts @@ -29,7 +29,9 @@ async function getInstance(instanceName: string) { if (db.ENABLED) { const collection = dbserver .getClient() - .db(db.CONNECTION.DB_PREFIX_NAME + '-instances') + .db( + db.CONNECTION.DB_PREFIX_NAME + + db.CONNECTION.DB_PREFIX_FINAL_NAME) .collection(instanceName); return exists || (await collection.find({}).toArray()).length > 0; } diff --git a/src/whatsapp/models/chatwoot.model.ts b/src/whatsapp/models/chatwoot.model.ts index ed7c2ef0..433c2ba5 100644 --- a/src/whatsapp/models/chatwoot.model.ts +++ b/src/whatsapp/models/chatwoot.model.ts @@ -9,6 +9,7 @@ export class ChatwootRaw { token?: string; url?: string; name_inbox?: string; + id_inbox?: string; sign_msg?: boolean; number?: string; reopen_conversation?: boolean; @@ -22,6 +23,7 @@ const chatwootSchema = new Schema({ token: { type: String, required: true }, url: { type: String, required: true }, name_inbox: { type: String, required: true }, + id_inbox: { type: String, required: true }, sign_msg: { type: Boolean, required: true }, number: { type: String, required: true }, reopen_conversation: { type: Boolean, required: true }, diff --git a/src/whatsapp/models/contactOpenai.model.ts b/src/whatsapp/models/contactOpenai.model.ts new file mode 100644 index 00000000..c6015764 --- /dev/null +++ b/src/whatsapp/models/contactOpenai.model.ts @@ -0,0 +1,20 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class ContactOpenaiRaw { + _id?: string; + contact?: string; + enabled?: boolean; + owner: string; +} + +const contactOpenaiSchema = new Schema({ + _id: { type: String, _id: true }, + contact: { type: String, required: true, minlength: 1 }, + enabled: { type: Boolean, required: true }, + owner: { type: String, required: true, minlength: 1 }, +}); + +export const ContactOpenaiModel = dbserver?.model(ContactOpenaiRaw.name, contactOpenaiSchema, 'openai_contacts'); +export type IContactOpenaiModel = typeof ContactOpenaiModel; diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index 7903e5b5..98cb200f 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -11,3 +11,5 @@ export * from './sqs.model'; export * from './typebot.model'; export * from './webhook.model'; export * from './websocket.model'; +export * from './openai.model'; +export * from './contactOpenai.model'; diff --git a/src/whatsapp/models/openai.model.ts b/src/whatsapp/models/openai.model.ts new file mode 100644 index 00000000..4e863686 --- /dev/null +++ b/src/whatsapp/models/openai.model.ts @@ -0,0 +1,22 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class OpenaiRaw { + _id?: string; + chave?: string; + prompts?: string; + enabled?: boolean; + events?: string[]; +} + +const openaiSchema = new Schema({ + _id: { type: String, _id: true }, + chave: { type: String, required: true }, + prompts: { type: String, required: false }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, +}); + +export const OpenaiModel = dbserver?.model(OpenaiRaw.name, openaiSchema, 'openai'); +export type IOpenaiModel = typeof OpenaiModel; diff --git a/src/whatsapp/models/sqs.model.ts b/src/whatsapp/models/sqs.model.ts index 2d5f432f..3972f59e 100644 --- a/src/whatsapp/models/sqs.model.ts +++ b/src/whatsapp/models/sqs.model.ts @@ -15,4 +15,4 @@ const sqsSchema = new Schema({ }); export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs'); -export type ISqsModel = typeof SqsModel; +export type ISqsModel = typeof SqsModel; \ No newline at end of file diff --git a/src/whatsapp/repository/openai.repository.ts b/src/whatsapp/repository/openai.repository.ts new file mode 100644 index 00000000..23711e01 --- /dev/null +++ b/src/whatsapp/repository/openai.repository.ts @@ -0,0 +1,153 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IContactOpenaiModel, ContactOpenaiRaw, IOpenaiModel, OpenaiRaw } from '../models'; + +export class OpenaiRepository extends Repository { + constructor( + private readonly openaiModel: IOpenaiModel, + private readonly contactopenaiModel: IContactOpenaiModel, + private readonly configService: ConfigService + ) { + super(configService); + } + + private readonly logger = new Logger('OpenaiRepository'); + + public async create(data: OpenaiRaw, instance: string): Promise { + try { + this.logger.verbose('creating openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving openai to db'); + const insert = await this.openaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('openai saved to db: ' + insert.modifiedCount + ' openai'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving openai to store'); + + this.writeStore({ + path: join(this.storePath, 'openai'), + fileName: instance, + data, + }); + + this.logger.verbose('openai saved to store in path: ' + join(this.storePath, 'openai') + '/' + instance); + + this.logger.verbose('openai created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + public async createContact(data: ContactOpenaiRaw, instance: string): Promise { + try { + this.logger.verbose('creating contact openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving openai to db'); + var resultado = await this.openaiModel.findOne({ owner: instance, contact: data.contact }); + if(!resultado){ + const insert = await this.contactopenaiModel.insertMany({ ...data }); + + this.logger.verbose('openai saved to db: ' + insert.length + ' openai_contacts'); + return { insertCount: insert.length }; + + }else{ + const contacts = [] + contacts[0] = { + updateOne: { + filter: { owner: data.owner, contact: data.contact }, + update: { ...data }, + upsert: true, + }, + }; + + const { nModified } = await this.contactopenaiModel.bulkWrite(contacts); + + this.logger.verbose('contacts updated in db: ' + nModified + ' contacts'); + return { insertCount: nModified }; + } + + } + + this.logger.verbose('saving openai to store'); + + this.writeStore({ + path: join(this.storePath, 'openai_contact'), + fileName: instance, + data, + }); + + this.logger.verbose('openai contact saved to store in path: ' + join(this.storePath, 'openai_contact') + '/' + instance); + + this.logger.verbose('openai contact created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + return await this.openaiModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai', instance + '.json'), { + encoding: 'utf-8', + }), + ) as OpenaiRaw; + } catch (error) { + return {}; + } + } + + public async findContact(instance: string, contact: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + + return await this.contactopenaiModel.findOne({ owner: instance,contact: contact}); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai_contact', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ContactOpenaiRaw; + } catch (error) { + + return ; + } + } + + public async findContactAll(instance: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + return await this.contactopenaiModel.find({ owner: instance }); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai_contact', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ContactOpenaiRaw; + } catch (error) { + + return; + } + } +} diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index ab4da1e3..1fd1bed9 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -13,6 +13,7 @@ import { MessageRepository } from './message.repository'; import { MessageUpRepository } from './messageUp.repository'; import { ProxyRepository } from './proxy.repository'; import { RabbitmqRepository } from './rabbitmq.repository'; +import { OpenaiRepository } from './openai.repository'; import { SettingsRepository } from './settings.repository'; import { SqsRepository } from './sqs.repository'; import { TypebotRepository } from './typebot.repository'; @@ -29,6 +30,8 @@ export class RepositoryBroker { public readonly settings: SettingsRepository, public readonly websocket: WebsocketRepository, public readonly rabbitmq: RabbitmqRepository, + public readonly openai: OpenaiRepository, + public readonly openai_contact: OpenaiRepository, public readonly sqs: SqsRepository, public readonly typebot: TypebotRepository, public readonly proxy: ProxyRepository, @@ -63,9 +66,10 @@ export class RepositoryBroker { const webhookDir = join(storePath, 'webhook'); const chatwootDir = join(storePath, 'chatwoot'); const settingsDir = join(storePath, 'settings'); + const sqsDir = join(storePath, 'sqs'); const websocketDir = join(storePath, 'websocket'); const rabbitmqDir = join(storePath, 'rabbitmq'); - const sqsDir = join(storePath, 'sqs'); + const openaiDir = join(storePath, 'openai'); const typebotDir = join(storePath, 'typebot'); const proxyDir = join(storePath, 'proxy'); const chamaaiDir = join(storePath, 'chamaai'); @@ -103,6 +107,12 @@ export class RepositoryBroker { this.logger.verbose('creating settings dir: ' + settingsDir); fs.mkdirSync(settingsDir, { recursive: true }); } + + if (!fs.existsSync(sqsDir)) { + this.logger.verbose('creating sqs dir: ' + sqsDir); + fs.mkdirSync(sqsDir, { recursive: true }); + } + if (!fs.existsSync(websocketDir)) { this.logger.verbose('creating websocket dir: ' + websocketDir); fs.mkdirSync(websocketDir, { recursive: true }); @@ -111,9 +121,9 @@ export class RepositoryBroker { this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir); fs.mkdirSync(rabbitmqDir, { recursive: true }); } - if (!fs.existsSync(sqsDir)) { - this.logger.verbose('creating sqs dir: ' + sqsDir); - fs.mkdirSync(sqsDir, { recursive: true }); + if (!fs.existsSync(openaiDir)) { + this.logger.verbose('creating openai dir: ' + openaiDir); + fs.mkdirSync(openaiDir, { recursive: true }); } if (!fs.existsSync(typebotDir)) { this.logger.verbose('creating typebot dir: ' + typebotDir); diff --git a/src/whatsapp/repository/sqs.repository.ts b/src/whatsapp/repository/sqs.repository.ts index 50ea1cd3..9e398e1b 100644 --- a/src/whatsapp/repository/sqs.repository.ts +++ b/src/whatsapp/repository/sqs.repository.ts @@ -59,4 +59,4 @@ export class SqsRepository extends Repository { return {}; } } -} +} \ No newline at end of file diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index fbe28ddd..4cb5bf82 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -11,6 +11,7 @@ import { GroupRouter } from './group.router'; import { InstanceRouter } from './instance.router'; import { ProxyRouter } from './proxy.router'; import { RabbitmqRouter } from './rabbitmq.router'; +import { OpenaiRouter } from './openai.router'; import { MessageRouter } from './sendMessage.router'; import { SettingsRouter } from './settings.router'; import { SqsRouter } from './sqs.router'; @@ -39,9 +40,9 @@ router .get('/', (req, res) => { res.status(HttpStatus.OK).json({ status: HttpStatus.OK, - message: 'Welcome to the Evolution API, it is working!', - version: packageJson.version, - documentation: `${req.protocol}://${req.get('host')}/docs`, + message: 'Api', + // version: packageJson.version, + // documentation: `${req.protocol}://${req.get('host')}/docs`, }); }) .use('/instance', new InstanceRouter(configService, ...guards).router) @@ -54,7 +55,7 @@ router .use('/settings', new SettingsRouter(...guards).router) .use('/websocket', new WebsocketRouter(...guards).router) .use('/rabbitmq', new RabbitmqRouter(...guards).router) - .use('/sqs', new SqsRouter(...guards).router) + .use('/openai', new OpenaiRouter(...guards).router) .use('/typebot', new TypebotRouter(...guards).router) .use('/proxy', new ProxyRouter(...guards).router) .use('/chamaai', new ChamaaiRouter(...guards).router); diff --git a/src/whatsapp/routers/instance.router.ts b/src/whatsapp/routers/instance.router.ts index 96a1a5da..b73222ac 100644 --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -32,7 +32,15 @@ export class InstanceRouter extends RouterBroker { execute: (instance) => instanceController.createInstance(instance), }); - return res.status(HttpStatus.CREATED).json(response); + + if (req.query['qrcode']) { + return res.status(HttpStatus.OK).render('qrcode', { + qrcode: response.qrcode.base64, + }); + } else { + return res.status(HttpStatus.CREATED).json(response); + } + //return res.status(HttpStatus.CREATED).json(response); }) .put(this.routerPath('restart'), ...guards, async (req, res) => { logger.verbose('request received in restartInstance'); @@ -50,6 +58,22 @@ export class InstanceRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .get(this.routerPath('qr'), ...guards, async (req, res) => { + logger.verbose('request received in get qrCode'); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => instanceController.qrInstance(instance), + }); + return res.status(HttpStatus.OK).render('qrcode', { + qrcode: response, + }); + + }) .get(this.routerPath('connect'), ...guards, async (req, res) => { logger.verbose('request received in connectInstance'); logger.verbose('request body: '); diff --git a/src/whatsapp/routers/openai.router.ts b/src/whatsapp/routers/openai.router.ts new file mode 100644 index 00000000..b3a4c09a --- /dev/null +++ b/src/whatsapp/routers/openai.router.ts @@ -0,0 +1,86 @@ +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, openaiSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { openaiController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('OpenaiRouter'); + +export class OpenaiRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: openaiSchema, + ClassRef: OpenaiDto, + execute: (instance, data) => openaiController.createOpenai(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => openaiController.findOpenai(instance), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('contact'), ...guards, async (req, res) => { + logger.verbose('request received in setOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: openaiSchema, + ClassRef: ContactOpenaiDto, + execute: (instance, data) => openaiController.createContactOpenai(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + + .get(this.routerPath('findcontact'), ...guards, async (req, res) => { + logger.verbose('request received in findOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => openaiController.findContactOpenai(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/sqs.router.ts b/src/whatsapp/routers/sqs.router.ts index e1bf8e93..9ae2a2ab 100644 --- a/src/whatsapp/routers/sqs.router.ts +++ b/src/whatsapp/routers/sqs.router.ts @@ -49,4 +49,4 @@ export class SqsRouter extends RouterBroker { } public readonly router = Router(); -} +} \ No newline at end of file diff --git a/src/whatsapp/services/._whatsapp.service.ts b/src/whatsapp/services/._whatsapp.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..57e853200a90c5a90263df30b8309f25e9c5269d GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103v`u|ts zJ46S=0-$mMG%bukK2%&PIX_n~v7jI)RWB#8xTLf=H6 conversation.inbox_id == filterInbox.id); + conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == idInboxChat); if (this.provider.conversation_pending) { await client.conversations.toggleStatus({ @@ -545,7 +558,7 @@ export class ChatwootService { } } else { conversation = contactConversations.payload.find( - (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, + (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == idInboxChat, ); } this.logger.verbose('return conversation if exists'); @@ -559,7 +572,7 @@ export class ChatwootService { this.logger.verbose('create conversation in chatwoot'); const data = { contact_id: contactId.toString(), - inbox_id: filterInbox.id.toString(), + inbox_id: idInboxChat.toString(), }; if (this.provider.conversation_pending) { @@ -685,20 +698,31 @@ export class ChatwootService { return null; } - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - const filterInbox = await this.getInbox(instance); + if(this.provider?.id_inbox){ - if (!filterInbox) { - this.logger.warn('inbox not found'); - return null; + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: this.provider?.id_inbox, + }); + + }else{ + + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + const filterInbox = await this.getInbox(instance); + + if (!filterInbox) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); } - this.logger.verbose('find conversation in chatwoot'); - const findConversation = await client.conversations.list({ - accountId: this.provider.account_id, - inboxId: filterInbox.id, - }); - if (!findConversation) { this.logger.warn('conversation not found'); return null; @@ -805,20 +829,32 @@ export class ChatwootService { return null; } - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - const filterInbox = await this.getInbox(instance); - if (!filterInbox) { - this.logger.warn('inbox not found'); - return null; + if (this.provider?.id_inbox) { + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: this.provider?.id_inbox, + }); + + } else { + + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + const filterInbox = await this.getInbox(instance); + + if (!filterInbox) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); } - this.logger.verbose('find conversation in chatwoot'); - const findConversation = await client.conversations.list({ - accountId: this.provider.account_id, - inboxId: filterInbox.id, - }); - if (!findConversation) { this.logger.warn('conversation not found'); return null; @@ -1053,6 +1089,29 @@ export class ChatwootService { formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived; } + if (body.content_attributes.in_reply_to){ + var idResposta = (body.content_attributes.in_reply_to-1); + var conversationId = body.conversation.contact_inbox.inbox_id; + let config = { + method: 'get', + maxBodyLength: Infinity, + url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversationId}/messages?after=` + idResposta, + headers: { + 'api_access_token': this.provider.token + } + }; + try { + const { data } = await axios.request(config); + if (data.payload[0] && data.payload[0].id == body.content_attributes.in_reply_to) { + formatText = formatText +`\n\nResposta à: \n` + data.payload[0].content + ``; + } + this.logger.verbose('data sent'); + } catch (error) { + this.logger.error(error); + } + } + + for (const message of body.conversation.messages) { this.logger.verbose('check if message is media'); if (message.attachments && message.attachments.length > 0) { @@ -1522,14 +1581,20 @@ export class ChatwootService { if (event === 'status.instance') { this.logger.verbose('event status.instance'); const data = body; - const inbox = await this.getInbox(instance); - if (!inbox) { - this.logger.warn('inbox not found'); - return; + if (this.provider?.id_inbox) { + var msgStatus = `⚡️ Instance status ${this.provider?.name_inbox}: ${data.status}`; + } else { + const inbox = await this.getInbox(instance); + + if (!inbox) { + this.logger.warn('inbox not found'); + return; + } + + var msgStatus = `⚡️ Instance status ${inbox.name}: ${data.status}`; } - const msgStatus = `⚡️ Instance status ${inbox.name}: ${data.status}`; this.logger.verbose('send message to chatwoot'); await this.createBotMessage(instance, msgStatus, 'incoming'); diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 45cd4a1f..b696d165 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -24,6 +24,7 @@ import { TypebotModel, WebhookModel, WebsocketModel, + } from '../models'; import { RepositoryBroker } from '../repository/repository.manager'; import { WAStartupService } from './whatsapp.service'; @@ -45,7 +46,10 @@ export class WAMonitoringService { Object.assign(this.redis, configService.get('REDIS')); this.dbInstance = this.db.ENABLED - ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') + ? this.repository.dbServer?.db( + this.db.CONNECTION.DB_PREFIX_NAME + + this.db.CONNECTION.DB_PREFIX_FINAL_NAME + ) : undefined; } @@ -122,7 +126,7 @@ export class WAMonitoringService { if (this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { instanceData.instance['serverUrl'] = this.configService.get('SERVER').URL; - + instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey; instanceData.instance['chatwoot'] = chatwoot; @@ -239,21 +243,23 @@ export class WAMonitoringService { this.logger.verbose('cleaning store database instance: ' + instanceName); + await AuthModel.deleteMany({ owner: instanceName }); await ChatModel.deleteMany({ owner: instanceName }); await ContactModel.deleteMany({ owner: instanceName }); - await MessageUpModel.deleteMany({ owner: instanceName }); await MessageModel.deleteMany({ owner: instanceName }); - - await AuthModel.deleteMany({ _id: instanceName }); + + await MessageUpModel.deleteMany({ owner: instanceName }); await WebhookModel.deleteMany({ _id: instanceName }); await ChatwootModel.deleteMany({ _id: instanceName }); + await ChamaaiModel.deleteMany({ _id: instanceName }); await ProxyModel.deleteMany({ _id: instanceName }); await RabbitmqModel.deleteMany({ _id: instanceName }); await TypebotModel.deleteMany({ _id: instanceName }); await WebsocketModel.deleteMany({ _id: instanceName }); + await SettingsModel.deleteMany({ _id: instanceName }); - + return; } @@ -277,6 +283,7 @@ export class WAMonitoringService { const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); instance.instanceName = name; this.logger.verbose('Instance loaded: ' + name); + await instance.connectToWhatsapp(); this.logger.verbose('connectToWhatsapp: ' + name); diff --git a/src/whatsapp/services/openai.service.ts b/src/whatsapp/services/openai.service.ts new file mode 100644 index 00000000..f80008ed --- /dev/null +++ b/src/whatsapp/services/openai.service.ts @@ -0,0 +1,57 @@ +import { Logger } from '../../config/logger.config'; +import { initQueues } from '../../libs/amqp.server'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { OpenaiRaw } from '../models'; +import { WAMonitoringService } from './monitor.service'; + +export class OpenaiService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(OpenaiService.name); + + public create(instance: InstanceDto, data: OpenaiDto) { + this.logger.verbose('create openai: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setOpenai(data); + return { openai: { ...instance, openai: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find openai: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findOpenai(); + + if (Object.keys(result).length === 0) { + throw new Error('openai not found'); + } + + return result; + } catch (error) { + return { chave: '', enabled: false, events: [] }; + } + } + + public createContact(instance: InstanceDto, data: ContactOpenaiDto) { + this.logger.verbose('create openai: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setContactOpenai(data); + return { openai: { ...instance, openai: data } }; + } + + + public async findContact(instance: InstanceDto): Promise { + try { + this.logger.verbose('find openai: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findContactOpenai(); + + if (Object.keys(result).length === 0) { + throw new Error('openai not found'); + } + + return result; + } catch (error) { + return { chave: '', enabled: false, events: [] }; + } + } + +} diff --git a/src/whatsapp/services/sqs.service.ts b/src/whatsapp/services/sqs.service.ts index 236d4ceb..06b1ae59 100644 --- a/src/whatsapp/services/sqs.service.ts +++ b/src/whatsapp/services/sqs.service.ts @@ -6,7 +6,7 @@ import { SqsRaw } from '../models'; import { WAMonitoringService } from './monitor.service'; export class SqsService { - constructor(private readonly waMonitor: WAMonitoringService) {} + constructor(private readonly waMonitor: WAMonitoringService) { } private readonly logger = new Logger(SqsService.name); @@ -32,4 +32,4 @@ export class SqsService { return { enabled: false, events: [] }; } } -} +} \ No newline at end of file diff --git a/src/whatsapp/services/typebot.service.ts b/src/whatsapp/services/typebot.service.ts index 14b04383..a272115a 100644 --- a/src/whatsapp/services/typebot.service.ts +++ b/src/whatsapp/services/typebot.service.ts @@ -9,7 +9,8 @@ import { Events } from '../types/wa.types'; import { WAMonitoringService } from './monitor.service'; export class TypebotService { - constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {} + //constructor(private readonly waMonitor: WAMonitoringService) {} + constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { } private readonly logger = new Logger(TypebotService.name); @@ -177,6 +178,8 @@ export class TypebotService { }; try { + //const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); + const version = this.configService.get('TYPEBOT').API_VERSION; const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); @@ -267,6 +270,8 @@ export class TypebotService { }; try { + //const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); + const version = this.configService.get('TYPEBOT').API_VERSION; const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); @@ -386,7 +391,7 @@ export class TypebotService { } if (element.underline) { - text = `*${text}*`; + text = `~${text}~`; } if (element.url) { @@ -498,7 +503,7 @@ export class TypebotService { const listening_from_me = findTypebot.listening_from_me; const session = sessions.find((session) => session.remoteJid === remoteJid); - + try { if (session && expire && expire > 0) { const now = Date.now(); @@ -571,6 +576,7 @@ export class TypebotService { }; try { + //const request = await axios.post(url + '/api/v1/sendMessage', reqData); const version = this.configService.get('TYPEBOT').API_VERSION; const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); @@ -658,9 +664,9 @@ export class TypebotService { let request: any; try { + //request = await axios.post(url + '/api/v1/sendMessage', reqData); const version = this.configService.get('TYPEBOT').API_VERSION; request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); - await this.sendWAMessage( instance, remoteJid, @@ -739,6 +745,7 @@ export class TypebotService { sessionId: session.sessionId.split('-')[1], }; + //const request = await axios.post(url + '/api/v1/sendMessage', reqData); const version = this.configService.get('TYPEBOT').API_VERSION; const request = await axios.post(`${url}/api/${version}/sendMessage`, reqData); @@ -755,5 +762,249 @@ export class TypebotService { this.logger.error(error); return; } + + /* + if (session && expire && expire > 0) { + const now = Date.now(); + + const diff = now - session.updateAt; + + const diffInMinutes = Math.floor(diff / 1000 / 60); + + if (diffInMinutes > expire) { + //sessions.splice(sessions.indexOf(session), 1); + const newSessions = await this.clearSessions(instance, remoteJid); + + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + //sessions: sessions, + sessions: newSessions, + remoteJid: remoteJid, + pushName: msg.pushName, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } + + return; + } + } + + + if (session && session.status !== 'opened') { + return; + } + + if (!session) { + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions: sessions, + remoteJid: remoteJid, + pushName: msg.pushName, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } + return; + } + + sessions.map((session) => { + if (session.remoteJid === remoteJid) { + session.updateAt = Date.now(); + } + }); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: session.sessionId.split('-')[1], + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + + return; + + */ } } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 5c3edc44..5a15c56c 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -115,7 +115,8 @@ import { SendTextDto, StatusMessage, } from '../dto/sendMessage.dto'; -import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, SqsRaw, TypebotRaw } from '../models'; +import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, OpenaiRaw, SettingsRaw, SqsRaw, TypebotRaw, ContactOpenaiRaw } from '../models'; + import { ChatRaw } from '../models/chat.model'; import { ChatwootRaw } from '../models/chatwoot.model'; import { ContactRaw } from '../models/contact.model'; @@ -130,7 +131,51 @@ import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types' import { waMonitor } from '../whatsapp.module'; import { ChamaaiService } from './chamaai.service'; import { ChatwootService } from './chatwoot.service'; +//import { SocksProxyAgent } from './socks-proxy-agent'; import { TypebotService } from './typebot.service'; + +import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai" + +import { openai, OpenAIService } from "../../libs/openai" +import { redis } from "../../libs/redis" + +import { initPrompt } from "../../utils/initPrompt" +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; + +// https://wa.me/+5512982754592 +interface CustomerChat { + status?: "open" | "closed" + orderCode: string + chatAt: string + customer: { + name: string + phone: string + } + messages: ChatCompletionRequestMessage[] + orderSummary?: string +} + +async function completion( + apiKey: string, + messages: ChatCompletionRequestMessage[] +): Promise { + + const configuration = new Configuration({ + apiKey: apiKey, + }) + + const openai = new OpenAIApi(configuration) + + const completion = await openai.createChatCompletion({ + model: "gpt-3.5-turbo", + temperature: 0, + max_tokens: 256, + messages, + }) + + return completion.data.choices[0].message?.content +} + export class WAStartupService { constructor( private readonly configService: ConfigService, @@ -151,6 +196,7 @@ export class WAStartupService { private readonly localSettings: wa.LocalSettings = {}; private readonly localWebsocket: wa.LocalWebsocket = {}; private readonly localRabbitmq: wa.LocalRabbitmq = {}; + private readonly localOpenai: wa.LocalOpenai = {}; private readonly localSqs: wa.LocalSqs = {}; public readonly localTypebot: wa.LocalTypebot = {}; private readonly localProxy: wa.LocalProxy = {}; @@ -166,6 +212,7 @@ export class WAStartupService { private chatwootService = new ChatwootService(waMonitor, this.configService); + //private typebotService = new TypebotService(waMonitor); private typebotService = new TypebotService(waMonitor, this.configService); private chamaaiService = new ChamaaiService(waMonitor, this.configService); @@ -216,7 +263,10 @@ export class WAStartupService { this.logger.verbose('Database enabled, trying to get from database'); const collection = dbserver .getClient() - .db(this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') + .db( + this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + + this.configService.get('DATABASE').CONNECTION.DB_PREFIX_FINAL_NAME + ) .collection(this.instanceName); const data = await collection.findOne({ _id: 'creds' }); if (data) { @@ -465,6 +515,72 @@ export class WAStartupService { return data; } + private async loadOpenai() { + this.logger.verbose('Loading openai'); + const data = await this.repository.openai.find(this.instanceName); + + this.localOpenai.chave = data?.chave; + this.logger.verbose(`Openai chave: ${this.localOpenai.chave}`); + + this.localOpenai.enabled = data?.enabled; + this.logger.verbose(`Openai enabled: ${this.localOpenai.enabled}`); + + this.localOpenai.events = data?.events; + this.logger.verbose(`Openai events: ${this.localOpenai.events}`); + + this.logger.verbose('Openai loaded'); + } + + public async setOpenai(data: OpenaiRaw) { + this.logger.verbose('Setting openai'); + await this.repository.openai.create(data, this.instanceName); + this.logger.verbose(`Openai events: ${data.events}`); + Object.assign(this.localOpenai, data); + this.logger.verbose('Openai set'); + } + + public async setContactOpenai(data: ContactOpenaiRaw) { + this.logger.verbose('Setting openai'); + await this.repository.openai.createContact(data, this.instanceName); + + this.logger.verbose(`Openai events: ${data.enabled}`); + Object.assign(this.localOpenai, data); + this.logger.verbose('Openai set'); + } + + public async findContactOpenai() { + this.logger.verbose('Finding openai'); + const data = await this.repository.openai.findContactAll(this.instanceName); + + if (!data) { + this.logger.verbose('Openai not found'); + throw new NotFoundException('Openai not found'); + } + + return data; + } + + public async findOpenai() { + this.logger.verbose('Finding openai'); + const data = await this.repository.openai.find(this.instanceName); + + if (!data) { + this.logger.verbose('Openai not found'); + throw new NotFoundException('Openai not found'); + } + + this.logger.verbose(`Openai events: ${data.events}`); + return data; + } + + public async removeOpenaiQueues() { + this.logger.verbose('Removing openai'); + + if (this.localOpenai.enabled) { + removeQueues(this.instanceName, this.localOpenai.events); + } + } + private async loadRabbitmq() { this.logger.verbose('Loading rabbitmq'); const data = await this.repository.rabbitmq.find(this.instanceName); @@ -507,6 +623,7 @@ export class WAStartupService { } } + private async loadSqs() { this.logger.verbose('Loading sqs'); const data = await this.repository.sqs.find(this.instanceName); @@ -626,10 +743,11 @@ export class WAStartupService { this.logger.verbose(`Proxy proxy: ${data.proxy}`); Object.assign(this.localProxy, data); this.logger.verbose('Proxy set'); - + //this.reloadConnection(); if (reload) { this.reloadConnection(); } + //this.client?.ws?.close(); } public async findProxy() { @@ -768,6 +886,7 @@ export class WAStartupService { } } + if (this.localSqs.enabled) { const sqs = getSQS(); @@ -1299,14 +1418,18 @@ export class WAStartupService { let options; if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy.proxy); - + this.logger.verbose('Proxy enabled'); + // options = { + // agent: new ProxyAgent(this.localProxy.proxy as any), + // fetchAgent: new ProxyAgent(this.localProxy.proxy as any), + // }; if (this.localProxy.proxy.includes('proxyscrape')) { const response = await axios.get(this.localProxy.proxy); const text = response.data; const proxyUrls = text.split('\r\n'); const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); const proxyUrl = 'http://' + proxyUrls[rand]; + console.log(proxyUrl); options = { agent: new ProxyAgent(proxyUrl as any), }; @@ -1656,9 +1779,132 @@ export class WAStartupService { let messageRaw: MessageRaw; + if (received.key.remoteJid.includes('@g.us')) { + }else{ + + if (messages[0].message.conversation){ + + const openai = await this.repository.openai_contact.find(this.instance.name); + + if (openai && openai?.chave && openai?.enabled == true && openai?.prompts){ + + const customerPhone = `+${received.key.remoteJid.replace("@s.whatsapp.net", "")}` + const customerName = messages[0].pushName + + const contactOpenaiC = await this.repository.openai_contact.findContact(this.instance.name, customerPhone); + if (!contactOpenaiC){ + var data = new ContactOpenaiDto; + data.contact = customerPhone; + data.enabled = true; + data.owner = this.instance.name; + + await this.repository.openai_contact.createContact(data, this.instance.name) + } + + const contactOpenai = await this.repository.openai_contact.findContact(this.instance.name, customerPhone); + + if(contactOpenai?.enabled == true){ + + const customerKey = `instancia:${this.instance.name}customer:${customerPhone}:chat` + const orderCode = `#sk-${("00000" + Math.random()).slice(-5)}` + + const lastChat = JSON.parse((await redis.get(customerKey)) || "{}") + var storeName = 'Nome'; + const customerChat: CustomerChat = + lastChat?.status === "open" + ? (lastChat as CustomerChat) + : { + status: "open", + orderCode, + chatAt: new Date().toISOString(), + customer: { + name: customerName, + phone: customerPhone, + }, + messages: [ + { + role: "system", + content: initPrompt(storeName, orderCode, openai?.prompts), + }, + ], + orderSummary: "", + } + + this.logger.verbose(customerPhone+" 👤 "+ messages[0].message.conversation) + + customerChat.messages.push({ + role: "user", + content: messages[0].message.conversation, + }) + + const content = + (await completion(openai.chave, + customerChat.messages)) || "Não entendi..." + + customerChat.messages.push({ + role: "assistant", + content, + }) + + this.logger.verbose(customerPhone + " 🤖 "+ content) + + var dadosMessage = { + textMessage: received.message.conversation, + }; + + let mentions: string[]; + + const linkPreview = true; + + let quoted: WAMessage; + + const option = { + quoted, + }; + + this.client.sendMessage( + received.key.remoteJid, + { + text: content, + mentions, + linkPreview: linkPreview, + } as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + + if ( + customerChat.status === "open" && + content.match(customerChat.orderCode) + ) { + customerChat.status = "closed" + + customerChat.messages.push({ + role: "user", + content: + "Gere um resumo de pedido para registro no sistema da pizzaria, quem está solicitando é um robô.", + }) + + const content = + (await completion(openai.chave,customerChat.messages)) || "Não entendi..." + + this.logger.verbose(customerPhone + " 📦 "+ content) + + customerChat.orderSummary = content + } + redis.set(customerKey, JSON.stringify(customerChat)) + + this.logger.verbose(received); + + } + } + + } + + } + if ( (this.localWebhook.webhook_base64 === true && received?.message.documentMessage) || - received?.message?.imageMessage + received?.message.imageMessage ) { const buffer = await downloadMediaMessage( { key: received.key, message: received?.message }, @@ -1714,6 +1960,7 @@ export class WAStartupService { ); } + //if (this.localTypebot.enabled) { const typebotSessionRemoteJid = this.localTypebot.sessions?.find( (session) => session.remoteJid === received.key.remoteJid, ); @@ -2060,7 +2307,9 @@ export class WAStartupService { private createJid(number: string): string { this.logger.verbose('Creating jid with number: ' + number); + //if (number.includes('@g.us') || number.includes('@s.whatsapp.net')) { if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) { + //this.logger.verbose('Number already contains @g.us or @s.whatsapp.net'); this.logger.verbose('Number already contains @g.us or @s.whatsapp.net or @lid'); return number; } @@ -2285,6 +2534,7 @@ export class WAStartupService { !message['conversation'] && sender !== 'status@broadcast' ) { + if (message['reactionMessage']) { this.logger.verbose('Sending reaction'); return await this.client.sendMessage( @@ -2314,6 +2564,7 @@ export class WAStartupService { ); } } + if (message['conversation']) { this.logger.verbose('Sending message'); return await this.client.sendMessage( @@ -2573,9 +2824,15 @@ export class WAStartupService { let mimetype: string; + // if (isURL(mediaMessage.media)) { + // mimetype = getMIMEType(mediaMessage.media); + // } else { + // mimetype = getMIMEType(mediaMessage.fileName); + // } + if (mediaMessage.mimetype) { mimetype = mediaMessage.mimetype; - } else { + }else{ if (isURL(mediaMessage.media)) { mimetype = getMIMEType(mediaMessage.media); } else { @@ -3229,6 +3486,7 @@ export class WAStartupService { await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd); this.logger.verbose('Groups add privacy updated'); + //this.client?.ws?.close(); this.reloadConnection(); return { diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 27582001..66549f5a 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -88,6 +88,12 @@ export declare namespace wa { enabled?: boolean; events?: string[]; }; + + export type LocalOpenai = { + chave?: string; + enabled?: boolean; + events?: string[]; + }; type Session = { remoteJid?: string; diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index 0bb9409f..e78b5f6c 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -10,6 +10,7 @@ import { GroupController } from './controllers/group.controller'; import { InstanceController } from './controllers/instance.controller'; import { ProxyController } from './controllers/proxy.controller'; import { RabbitmqController } from './controllers/rabbitmq.controller'; +import { OpenaiController } from './controllers/openai.controller'; import { SendMessageController } from './controllers/sendMessage.controller'; import { SettingsController } from './controllers/settings.controller'; import { SqsController } from './controllers/sqs.controller'; @@ -27,6 +28,8 @@ import { MessageUpModel, ProxyModel, RabbitmqModel, + OpenaiModel, + ContactOpenaiModel, SettingsModel, SqsModel, TypebotModel, @@ -42,6 +45,7 @@ import { MessageRepository } from './repository/message.repository'; import { MessageUpRepository } from './repository/messageUp.repository'; import { ProxyRepository } from './repository/proxy.repository'; import { RabbitmqRepository } from './repository/rabbitmq.repository'; +import { OpenaiRepository } from './repository/openai.repository'; import { RepositoryBroker } from './repository/repository.manager'; import { SettingsRepository } from './repository/settings.repository'; import { SqsRepository } from './repository/sqs.repository'; @@ -54,6 +58,7 @@ import { ChatwootService } from './services/chatwoot.service'; import { WAMonitoringService } from './services/monitor.service'; import { ProxyService } from './services/proxy.service'; import { RabbitmqService } from './services/rabbitmq.service'; +import { OpenaiService } from './services/openai.service'; import { SettingsService } from './services/settings.service'; import { SqsService } from './services/sqs.service'; import { TypebotService } from './services/typebot.service'; @@ -72,9 +77,10 @@ const websocketRepository = new WebsocketRepository(WebsocketModel, configServic const proxyRepository = new ProxyRepository(ProxyModel, configService); const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService); const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService); -const sqsRepository = new SqsRepository(SqsModel, configService); +const openaiRepository = new OpenaiRepository(OpenaiModel,ContactOpenaiModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); const settingsRepository = new SettingsRepository(SettingsModel, configService); +const sqsRepository = new SqsRepository(SqsModel, configService); const authRepository = new AuthRepository(AuthModel, configService); export const repository = new RepositoryBroker( @@ -87,6 +93,8 @@ export const repository = new RepositoryBroker( settingsRepository, websocketRepository, rabbitmqRepository, + openaiRepository, + openaiRepository, sqsRepository, typebotRepository, proxyRepository, @@ -130,6 +138,10 @@ const sqsService = new SqsService(waMonitor); export const sqsController = new SqsController(sqsService); +const openaiService = new OpenaiService(waMonitor); + +export const openaiController = new OpenaiController(openaiService); + const chatwootService = new ChatwootService(waMonitor, configService); export const chatwootController = new ChatwootController(chatwootService, configService); @@ -149,6 +161,7 @@ export const instanceController = new InstanceController( settingsService, websocketService, rabbitmqService, + openaiService, proxyService, sqsService, typebotService,