mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-26 18:38:39 -06:00
Integração com ChatGPT e id inbox chatwoot
This commit is contained in:
parent
41b2946cdc
commit
eadbba8ca6
5
env
Normal file
5
env
Normal file
@ -0,0 +1,5 @@
|
||||
OPENAI_API_KEY=""
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
REDIS_PASS=0
|
14
src/config.ts
Executable file
14
src/config.ts
Executable file
@ -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,
|
||||
},
|
||||
}
|
15
src/config/env.config.ts
Normal file → Executable file
15
src/config/env.config.ts
Normal file → Executable file
@ -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',
|
||||
},
|
||||
|
4
src/config/error.config.ts
Normal file → Executable file
4
src/config/error.config.ts
Normal file → Executable file
@ -8,7 +8,7 @@ export function onUnexpectedError() {
|
||||
stderr: process.stderr.fd,
|
||||
error,
|
||||
});
|
||||
// process.exit(1);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (error, origin) => {
|
||||
@ -18,6 +18,6 @@ export function onUnexpectedError() {
|
||||
stderr: process.stderr.fd,
|
||||
error,
|
||||
});
|
||||
// process.exit(1);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
0
src/config/event.config.ts
Normal file → Executable file
0
src/config/event.config.ts
Normal file → Executable file
0
src/config/logger.config.ts
Normal file → Executable file
0
src/config/logger.config.ts
Normal file → Executable file
0
src/config/path.config.ts
Normal file → Executable file
0
src/config/path.config.ts
Normal file → Executable file
39
src/dev-env.yml
Normal file → Executable file
39
src/dev-env.yml
Normal file → Executable file
@ -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,8 +86,14 @@ 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: ""
|
||||
@ -93,12 +102,15 @@ SQS:
|
||||
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>
|
||||
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,7 +146,7 @@ WEBHOOK:
|
||||
CHAMA_AI_ACTION: false
|
||||
# This event is used to send errors to the webhook
|
||||
ERRORS: false
|
||||
ERRORS_WEBHOOK: <url>
|
||||
ERRORS_WEBHOOK: ""
|
||||
|
||||
CONFIG_SESSION_PHONE:
|
||||
# Name that will be displayed on smartphone connection
|
||||
@ -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
|
||||
|
0
src/docs/swagger.conf.ts
Normal file → Executable file
0
src/docs/swagger.conf.ts
Normal file → Executable file
2
src/docs/swagger.yaml
Normal file → Executable file
2
src/docs/swagger.yaml
Normal file → Executable file
@ -25,7 +25,7 @@ info:
|
||||
</font>
|
||||
|
||||
[](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
|
||||
|
0
src/exceptions/400.exception.ts
Normal file → Executable file
0
src/exceptions/400.exception.ts
Normal file → Executable file
0
src/exceptions/401.exception.ts
Normal file → Executable file
0
src/exceptions/401.exception.ts
Normal file → Executable file
0
src/exceptions/403.exception.ts
Normal file → Executable file
0
src/exceptions/403.exception.ts
Normal file → Executable file
0
src/exceptions/404.exception.ts
Normal file → Executable file
0
src/exceptions/404.exception.ts
Normal file → Executable file
0
src/exceptions/500.exception.ts
Normal file → Executable file
0
src/exceptions/500.exception.ts
Normal file → Executable file
0
src/exceptions/index.ts
Normal file → Executable file
0
src/exceptions/index.ts
Normal file → Executable file
2
src/libs/amqp.server.ts
Normal file → Executable file
2
src/libs/amqp.server.ts
Normal file → Executable file
@ -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');
|
||||
|
3
src/libs/db.connect.ts
Normal file → Executable file
3
src/libs/db.connect.ts
Normal file → Executable file
@ -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']);
|
||||
|
34
src/libs/openai.ts
Executable file
34
src/libs/openai.ts
Executable file
@ -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;
|
||||
}
|
||||
|
||||
}
|
29
src/libs/redis.client.ts
Normal file → Executable file
29
src/libs/redis.client.ts
Normal file → Executable file
@ -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<string[]> {
|
||||
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;
|
||||
|
9
src/libs/redis.ts
Executable file
9
src/libs/redis.ts
Executable file
@ -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,
|
||||
})
|
0
src/libs/socket.server.ts
Normal file → Executable file
0
src/libs/socket.server.ts
Normal file → Executable file
0
src/libs/sqs.server.ts
Normal file → Executable file
0
src/libs/sqs.server.ts
Normal file → Executable file
0
src/main.ts
Normal file → Executable file
0
src/main.ts
Normal file → Executable file
49
src/prompts/contabilAgent.ts
Executable file
49
src/prompts/contabilAgent.ts
Executable file
@ -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?
|
||||
|
||||
`
|
94
src/prompts/pizzaAgent.ts
Executable file
94
src/prompts/pizzaAgent.ts
Executable file
@ -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
|
||||
`
|
13
src/utils/initPrompt.ts
Executable file
13
src/utils/initPrompt.ts
Executable file
@ -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)
|
||||
}
|
||||
}
|
0
src/utils/server-up.ts
Normal file → Executable file
0
src/utils/server-up.ts
Normal file → Executable file
9
src/utils/use-multi-file-auth-state-db.ts
Normal file → Executable file
9
src/utils/use-multi-file-auth-state-db.ts
Normal file → Executable file
@ -19,7 +19,10 @@ export async function useMultiFileAuthStateDb(
|
||||
const client = dbserver.getClient();
|
||||
|
||||
const collection = client
|
||||
.db(configService.get<Database>('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances')
|
||||
.db(
|
||||
configService.get<Database>('DATABASE').CONNECTION.DB_PREFIX_NAME +
|
||||
configService.get<Database>('DATABASE').CONNECTION.DB_PREFIX_FINAL_NAME
|
||||
)
|
||||
.collection(coll);
|
||||
|
||||
const writeData = async (data: any, key: string): Promise<any> => {
|
||||
@ -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) {
|
||||
@ -101,7 +106,7 @@ export async function useMultiFileAuthStateDb(
|
||||
},
|
||||
},
|
||||
saveCreds: async () => {
|
||||
return await writeData(creds, 'creds');
|
||||
return writeData(creds, 'creds');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
0
src/utils/use-multi-file-auth-state-redis-db.ts
Normal file → Executable file
0
src/utils/use-multi-file-auth-state-redis-db.ts
Normal file → Executable file
45
src/validate/validate.schema.ts
Normal file → Executable file
45
src/validate/validate.schema.ts
Normal file → Executable file
@ -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',
|
||||
|
0
src/whatsapp/abstract/abstract.repository.ts
Normal file → Executable file
0
src/whatsapp/abstract/abstract.repository.ts
Normal file → Executable file
0
src/whatsapp/abstract/abstract.router.ts
Normal file → Executable file
0
src/whatsapp/abstract/abstract.router.ts
Normal file → Executable file
0
src/whatsapp/controllers/chamaai.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/chamaai.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/chat.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/chat.controller.ts
Normal file → Executable file
2
src/whatsapp/controllers/chatwoot.controller.ts
Normal file → Executable file
2
src/whatsapp/controllers/chatwoot.controller.ts
Normal file → Executable file
@ -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);
|
||||
|
0
src/whatsapp/controllers/group.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/group.controller.ts
Normal file → Executable file
104
src/whatsapp/controllers/instance.controller.ts
Normal file → Executable file
104
src/whatsapp/controllers/instance.controller.ts
Normal file → Executable file
@ -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());
|
||||
}
|
||||
|
85
src/whatsapp/controllers/openai.controller.ts
Executable file
85
src/whatsapp/controllers/openai.controller.ts
Executable file
@ -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);
|
||||
}
|
||||
}
|
0
src/whatsapp/controllers/proxy.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/proxy.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/rabbitmq.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/rabbitmq.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/sendMessage.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/sendMessage.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/settings.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/settings.controller.ts
Normal file → Executable file
2
src/whatsapp/controllers/sqs.controller.ts
Normal file → Executable file
2
src/whatsapp/controllers/sqs.controller.ts
Normal file → Executable file
@ -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');
|
||||
|
0
src/whatsapp/controllers/typebot.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/typebot.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/views.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/views.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/webhook.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/webhook.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/websocket.controller.ts
Normal file → Executable file
0
src/whatsapp/controllers/websocket.controller.ts
Normal file → Executable file
0
src/whatsapp/dto/chamaai.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/chamaai.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/chat.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/chat.dto.ts
Normal file → Executable file
1
src/whatsapp/dto/chatwoot.dto.ts
Normal file → Executable file
1
src/whatsapp/dto/chatwoot.dto.ts
Normal file → Executable file
@ -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;
|
||||
|
5
src/whatsapp/dto/contactopenai.dto.ts
Executable file
5
src/whatsapp/dto/contactopenai.dto.ts
Executable file
@ -0,0 +1,5 @@
|
||||
export class ContactOpenaiDto {
|
||||
contact?: string;
|
||||
enabled: boolean;
|
||||
owner: string;
|
||||
}
|
0
src/whatsapp/dto/group.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/group.dto.ts
Normal file → Executable file
8
src/whatsapp/dto/instance.dto.ts
Normal file → Executable file
8
src/whatsapp/dto/instance.dto.ts
Normal file → Executable file
@ -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;
|
||||
}
|
||||
|
6
src/whatsapp/dto/openai.dto.ts
Executable file
6
src/whatsapp/dto/openai.dto.ts
Executable file
@ -0,0 +1,6 @@
|
||||
export class OpenaiDto {
|
||||
chave?: string;
|
||||
enabled: boolean;
|
||||
prompts?: string;
|
||||
events?: string[];
|
||||
}
|
0
src/whatsapp/dto/proxy.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/proxy.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/rabbitmq.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/rabbitmq.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/sendMessage.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/sendMessage.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/settings.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/settings.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/sqs.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/sqs.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/typebot.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/typebot.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/webhook.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/webhook.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/websocket.dto.ts
Normal file → Executable file
0
src/whatsapp/dto/websocket.dto.ts
Normal file → Executable file
0
src/whatsapp/guards/auth.guard.ts
Normal file → Executable file
0
src/whatsapp/guards/auth.guard.ts
Normal file → Executable file
4
src/whatsapp/guards/instance.guard.ts
Normal file → Executable file
4
src/whatsapp/guards/instance.guard.ts
Normal file → Executable file
@ -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;
|
||||
}
|
||||
|
0
src/whatsapp/models/auth.model.ts
Normal file → Executable file
0
src/whatsapp/models/auth.model.ts
Normal file → Executable file
0
src/whatsapp/models/chamaai.model.ts
Normal file → Executable file
0
src/whatsapp/models/chamaai.model.ts
Normal file → Executable file
0
src/whatsapp/models/chat.model.ts
Normal file → Executable file
0
src/whatsapp/models/chat.model.ts
Normal file → Executable file
2
src/whatsapp/models/chatwoot.model.ts
Normal file → Executable file
2
src/whatsapp/models/chatwoot.model.ts
Normal file → Executable file
@ -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<ChatwootRaw>({
|
||||
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 },
|
||||
|
0
src/whatsapp/models/contact.model.ts
Normal file → Executable file
0
src/whatsapp/models/contact.model.ts
Normal file → Executable file
20
src/whatsapp/models/contactOpenai.model.ts
Executable file
20
src/whatsapp/models/contactOpenai.model.ts
Executable file
@ -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<ContactOpenaiRaw>({
|
||||
_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;
|
2
src/whatsapp/models/index.ts
Normal file → Executable file
2
src/whatsapp/models/index.ts
Normal file → Executable file
@ -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';
|
||||
|
0
src/whatsapp/models/message.model.ts
Normal file → Executable file
0
src/whatsapp/models/message.model.ts
Normal file → Executable file
22
src/whatsapp/models/openai.model.ts
Executable file
22
src/whatsapp/models/openai.model.ts
Executable file
@ -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<OpenaiRaw>({
|
||||
_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;
|
0
src/whatsapp/models/proxy.model.ts
Normal file → Executable file
0
src/whatsapp/models/proxy.model.ts
Normal file → Executable file
0
src/whatsapp/models/rabbitmq.model.ts
Normal file → Executable file
0
src/whatsapp/models/rabbitmq.model.ts
Normal file → Executable file
0
src/whatsapp/models/settings.model.ts
Normal file → Executable file
0
src/whatsapp/models/settings.model.ts
Normal file → Executable file
0
src/whatsapp/models/sqs.model.ts
Normal file → Executable file
0
src/whatsapp/models/sqs.model.ts
Normal file → Executable file
0
src/whatsapp/models/typebot.model.ts
Normal file → Executable file
0
src/whatsapp/models/typebot.model.ts
Normal file → Executable file
0
src/whatsapp/models/webhook.model.ts
Normal file → Executable file
0
src/whatsapp/models/webhook.model.ts
Normal file → Executable file
0
src/whatsapp/models/websocket.model.ts
Normal file → Executable file
0
src/whatsapp/models/websocket.model.ts
Normal file → Executable file
0
src/whatsapp/repository/auth.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/auth.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chamaai.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chamaai.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chat.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chat.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chatwoot.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/chatwoot.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/contact.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/contact.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/message.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/message.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/messageUp.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/messageUp.repository.ts
Normal file → Executable file
153
src/whatsapp/repository/openai.repository.ts
Executable file
153
src/whatsapp/repository/openai.repository.ts
Executable file
@ -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<IInsert> {
|
||||
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<OpenaiRaw>({
|
||||
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<IInsert> {
|
||||
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<OpenaiRaw>({
|
||||
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<OpenaiRaw> {
|
||||
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<ContactOpenaiRaw> {
|
||||
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<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
0
src/whatsapp/repository/proxy.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/proxy.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/rabbitmq.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/rabbitmq.repository.ts
Normal file → Executable file
18
src/whatsapp/repository/repository.manager.ts
Normal file → Executable file
18
src/whatsapp/repository/repository.manager.ts
Normal file → Executable file
@ -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);
|
||||
|
0
src/whatsapp/repository/settings.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/settings.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/sqs.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/sqs.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/typebot.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/typebot.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/webhook.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/webhook.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/websocket.repository.ts
Normal file → Executable file
0
src/whatsapp/repository/websocket.repository.ts
Normal file → Executable file
0
src/whatsapp/routers/chamaai.router.ts
Normal file → Executable file
0
src/whatsapp/routers/chamaai.router.ts
Normal file → Executable file
0
src/whatsapp/routers/chat.router.ts
Normal file → Executable file
0
src/whatsapp/routers/chat.router.ts
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user