Merge branch 'ev2' into v2.0.0

This commit is contained in:
Stênio Aníbal 2024-08-20 11:26:34 -03:00
commit ec9e227413
21 changed files with 675 additions and 398 deletions

View File

@ -1,3 +1,10 @@
# 2.1.0 (develop)
### Features
* Improved layout manager
* Translation in manager: English, Portuguese, Spanish and French
# 2.0.10 (2024-08-16 16:23)
### Features

File diff suppressed because one or more lines are too long

381
manager/dist/assets/index-CNPbB3iJ.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<link rel="icon" type="image/png" href="/assets/images/evolution-logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Evolution Manager</title>
<script type="module" crossorigin src="/assets/index-Cqx_OwQi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DZ0gaAHg.css">
<script type="module" crossorigin src="/assets/index-CNPbB3iJ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BJ9JMAl_.css">
</head>
<body>
<div id="root"></div>

View File

@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "2.0.10",
"version": "2.1.0",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/main.js",
"type": "commonjs",

View File

@ -37,6 +37,7 @@ enum TriggerType {
all
keyword
none
advanced
}
enum TriggerOperator {

View File

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "TriggerType" ADD VALUE 'advanced';

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "IntegrationSession" ADD COLUMN "context" JSONB;

View File

@ -37,6 +37,7 @@ enum TriggerType {
all
keyword
none
advanced
}
enum TriggerOperator {
@ -393,6 +394,7 @@ model IntegrationSession {
pushName String?
status SessionStatus
awaitUser Boolean @default(false) @db.Boolean
context Json?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Message Message[]

View File

@ -5,6 +5,7 @@ import { WAMonitoringService } from '@api/services/monitor.service';
import { Auth, ConfigService, HttpServer, S3 } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { Dify, DifySetting, IntegrationSession, Message } from '@prisma/client';
import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
import { Readable } from 'stream';
@ -114,6 +115,23 @@ export class DifyService {
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.dify.findFirst({
where: {
triggerValue: data.triggerValue,
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
try {
const dify = await this.prismaRepository.dify.create({
data: {
@ -239,9 +257,25 @@ export class DifyService {
where: {
triggerOperator: data.triggerOperator,
triggerValue: data.triggerValue,
id: {
not: difyId,
},
id: { not: difyId },
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.dify.findFirst({
where: {
triggerValue: data.triggerValue,
id: { not: difyId },
instanceId: instanceId,
},
});
@ -727,6 +761,19 @@ export class DifyService {
if (findTriggerAll) return findTriggerAll;
const findTriggerAdvanced = await this.prismaRepository.dify.findMany({
where: {
enabled: true,
triggerType: 'advanced',
instanceId: instanceId,
},
});
for (const advanced of findTriggerAdvanced) {
if (advancedOperatorsSearch(content, advanced.triggerValue)) {
return advanced;
}
}
// Check for exact match
const findTriggerEquals = await this.prismaRepository.dify.findFirst({
where: {

View File

@ -29,7 +29,7 @@ export const difySchema: JSONSchema7 = {
botType: { type: 'string', enum: ['chatBot', 'textGenerator', 'agent', 'workflow'] },
apiUrl: { type: 'string' },
apiKey: { type: 'string' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none'] },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },

View File

@ -10,6 +10,7 @@ import { WAMonitoringService } from '@api/services/monitor.service';
import { ConfigService, Language, S3 } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { IntegrationSession, Message, OpenaiBot, OpenaiCreds, OpenaiSetting } from '@prisma/client';
import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
import { downloadMediaMessage } from 'baileys';
@ -238,6 +239,23 @@ export class OpenaiService {
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.openaiBot.findFirst({
where: {
triggerValue: data.triggerValue,
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
try {
const openaiBot = await this.prismaRepository.openaiBot.create({
data: {
@ -390,9 +408,25 @@ export class OpenaiService {
where: {
triggerOperator: data.triggerOperator,
triggerValue: data.triggerValue,
id: {
not: openaiBotId,
},
id: { not: openaiBotId },
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.openaiBot.findFirst({
where: {
triggerValue: data.triggerValue,
id: { not: openaiBotId },
instanceId: instanceId,
},
});
@ -452,7 +486,7 @@ export class OpenaiService {
const openaiBots = await this.prismaRepository.openaiBot.findMany({
where: {
instanceId: instanceId,
instanceId,
},
include: {
sessions: true,
@ -600,13 +634,14 @@ export class OpenaiService {
public async fetchDefaultSettings(instance: InstanceDto) {
try {
const instanceId = await this.prismaRepository.instance
.findFirst({
const instanceId = (
await this.prismaRepository.instance.findFirst({
select: { id: true },
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
)?.id;
const settings = await this.prismaRepository.openaiSetting.findFirst({
where: {
@ -931,6 +966,19 @@ export class OpenaiService {
if (findTriggerAll) return findTriggerAll;
const findTriggerAdvanced = await this.prismaRepository.openaiBot.findMany({
where: {
enabled: true,
triggerType: 'advanced',
instanceId: instanceId,
},
});
for (const advanced of findTriggerAdvanced) {
if (advancedOperatorsSearch(content, advanced.triggerValue)) {
return advanced;
}
}
// Check for exact match
const findTriggerEquals = await this.prismaRepository.openaiBot.findFirst({
where: {
@ -1070,7 +1118,7 @@ export class OpenaiService {
}, debounceTime * 1000);
}
public async sendOpenai(instance: InstanceDto, remoteJid: string, msg: Message) {
public async sendOpenai(instance: InstanceDto, remoteJid: string, pushName: string, msg: Message) {
try {
const settings = await this.prismaRepository.openaiSetting.findFirst({
where: {
@ -1193,7 +1241,7 @@ export class OpenaiService {
};
if (stopBotFromMe && key.fromMe && session) {
await this.prismaRepository.integrationSession.update({
session = await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
@ -1201,7 +1249,6 @@ export class OpenaiService {
status: 'paused',
},
});
return;
}
if (!listeningFromMe && key.fromMe) {
@ -1214,6 +1261,8 @@ export class OpenaiService {
await this.processOpenaiAssistant(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
pushName,
key.fromMe,
findOpenai,
session,
settings,
@ -1237,6 +1286,8 @@ export class OpenaiService {
await this.processOpenaiAssistant(
this.waMonitor.waInstances[instance.instanceName],
remoteJid,
pushName,
key.fromMe,
findOpenai,
session,
settings,
@ -1304,6 +1355,8 @@ export class OpenaiService {
private async initAssistantNewSession(
instance: any,
remoteJid: string,
pushName: string,
fromMe: boolean,
openaiBot: OpenaiBot,
settings: OpenaiSetting,
session: IntegrationSession,
@ -1320,7 +1373,7 @@ export class OpenaiService {
}
const messageData: any = {
role: 'user',
role: fromMe ? 'assistant' : 'user',
content: [{ type: 'text', text: content }],
};
@ -1342,6 +1395,11 @@ export class OpenaiService {
await this.client.beta.threads.messages.create(data.session.sessionId, messageData);
if (fromMe) {
sendTelemetry('/message/sendText');
return;
}
const runAssistant = await this.client.beta.threads.runs.create(data.session.sessionId, {
assistant_id: openaiBot.assistantId,
});
@ -1350,7 +1408,13 @@ export class OpenaiService {
await instance.client.sendPresenceUpdate('composing', remoteJid);
const response = await this.getAIResponse(data.session.sessionId, runAssistant.id, openaiBot.functionUrl);
const response = await this.getAIResponse(
data.session.sessionId,
runAssistant.id,
openaiBot.functionUrl,
remoteJid,
pushName,
);
await instance.client.sendPresenceUpdate('paused', remoteJid);
@ -1426,10 +1490,15 @@ export class OpenaiService {
}
}
private async getAIResponse(threadId: string, runId: string, functionUrl: string) {
private async getAIResponse(
threadId: string,
runId: string,
functionUrl: string,
remoteJid: string,
pushName: string,
) {
const getRun = await this.client.beta.threads.runs.retrieve(threadId, runId);
let toolCalls;
switch (getRun.status) {
case 'requires_action':
toolCalls = getRun?.required_action?.submit_tool_outputs?.tool_calls;
@ -1447,7 +1516,7 @@ export class OpenaiService {
try {
const { data } = await axios.post(functionUrl, {
name: functionName,
arguments: functionArgument,
arguments: { ...functionArgument, remoteJid, pushName },
});
output = JSON.stringify(data)
@ -1476,13 +1545,13 @@ export class OpenaiService {
}
}
return this.getAIResponse(threadId, runId, functionUrl);
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'queued':
await new Promise((resolve) => setTimeout(resolve, 1000));
return this.getAIResponse(threadId, runId, functionUrl);
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'in_progress':
await new Promise((resolve) => setTimeout(resolve, 1000));
return this.getAIResponse(threadId, runId, functionUrl);
return this.getAIResponse(threadId, runId, functionUrl, remoteJid, pushName);
case 'completed':
return await this.client.beta.threads.messages.list(threadId, {
run_id: runId,
@ -1498,12 +1567,14 @@ export class OpenaiService {
private async processOpenaiAssistant(
instance: any,
remoteJid: string,
pushName: string,
fromMe: boolean,
openaiBot: OpenaiBot,
session: IntegrationSession,
settings: OpenaiSetting,
content: string,
) {
if (session && session.status !== 'opened') {
if (session && session.status === 'closed') {
return;
}
@ -1535,25 +1606,35 @@ export class OpenaiService {
});
}
await this.initAssistantNewSession(instance, remoteJid, openaiBot, settings, session, content);
await this.initAssistantNewSession(
instance,
remoteJid,
pushName,
fromMe,
openaiBot,
settings,
session,
content,
);
return;
}
}
if (!session) {
await this.initAssistantNewSession(instance, remoteJid, openaiBot, settings, session, content);
await this.initAssistantNewSession(instance, remoteJid, pushName, fromMe, openaiBot, settings, session, content);
return;
}
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: false,
},
});
if (session.status !== 'paused')
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: false,
},
});
if (!content) {
if (settings.unknownMessage) {
@ -1607,7 +1688,7 @@ export class OpenaiService {
const threadId = session.sessionId;
const messageData: any = {
role: 'user',
role: fromMe ? 'assistant' : 'user',
content: [{ type: 'text', text: content }],
};
@ -1629,15 +1710,24 @@ export class OpenaiService {
await this.client.beta.threads.messages.create(threadId, messageData);
if (fromMe || session?.status === 'paused') {
sendTelemetry('/message/sendText');
return;
}
const runAssistant = await this.client.beta.threads.runs.create(threadId, {
assistant_id: openaiBot.assistantId,
additional_instructions: `WhatsappApiInfo:
Name: ${pushName}
RemoteJid: ${remoteJid}
`,
});
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
const response = await this.getAIResponse(threadId, runAssistant.id, openaiBot.functionUrl);
const response = await this.getAIResponse(threadId, runAssistant.id, openaiBot.functionUrl, remoteJid, pushName);
await instance.client.sendPresenceUpdate('paused', remoteJid);

View File

@ -35,7 +35,7 @@ export const openaiSchema: JSONSchema7 = {
assistantMessages: { type: 'array', items: { type: 'string' } },
userMessages: { type: 'array', items: { type: 'string' } },
maxTokens: { type: 'integer' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none'] },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },

View File

@ -6,6 +6,7 @@ import { Events } from '@api/types/wa.types';
import { Auth, ConfigService, HttpServer, S3, Typebot } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { Instance, IntegrationSession, Message, Typebot as TypebotModel } from '@prisma/client';
import { advancedOperatorsSearch } from '@utils/advancedOperatorsSearch';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
@ -113,6 +114,23 @@ export class TypebotService {
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.typebot.findFirst({
where: {
triggerValue: data.triggerValue,
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
try {
const typebot = await this.prismaRepository.typebot.create({
data: {
@ -250,6 +268,24 @@ export class TypebotService {
}
}
if (data.triggerType === 'advanced') {
if (!data.triggerValue) {
throw new Error('Trigger value is required');
}
const checkDuplicate = await this.prismaRepository.typebot.findFirst({
where: {
triggerValue: data.triggerValue,
id: { not: typebotId },
instanceId: instanceId,
},
});
if (checkDuplicate) {
throw new Error('Trigger already exists');
}
}
try {
const typebot = await this.prismaRepository.typebot.update({
where: {
@ -1287,6 +1323,19 @@ export class TypebotService {
if (findTriggerAll) return findTriggerAll;
const findTriggerAdvanced = await this.prismaRepository.typebot.findMany({
where: {
enabled: true,
triggerType: 'advanced',
instanceId: instanceId,
},
});
for (const advanced of findTriggerAdvanced) {
if (advancedOperatorsSearch(content, advanced.triggerValue)) {
return advanced;
}
}
// Check for exact match
const findTriggerEquals = await this.prismaRepository.typebot.findFirst({
where: {

View File

@ -28,7 +28,7 @@ export const typebotSchema: JSONSchema7 = {
description: { type: 'string' },
url: { type: 'string' },
typebot: { type: 'string' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none'] },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },

View File

@ -78,6 +78,9 @@ router
return res.status(HttpStatus.OK).json({
status: HttpStatus.OK,
message: 'Credentials are valid',
facebookAppId: process.env.FACEBOOK_APP_ID,
facebookConfigId: process.env.FACEBOOK_CONFIG_ID,
facebookUserToken: process.env.FACEBOOK_USER_TOKEN,
});
})
.use('/instance', new InstanceRouter(configService, ...guards).router)

View File

@ -1243,6 +1243,7 @@ export class BaileysStartupService extends ChannelStartupService {
await this.openaiService.sendOpenai(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
messageRaw.pushName,
messageRaw,
);
}
@ -2056,6 +2057,7 @@ export class BaileysStartupService extends ChannelStartupService {
await this.openaiService.sendOpenai(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
messageRaw.pushName,
messageRaw,
);
}

View File

@ -512,6 +512,7 @@ export class BusinessStartupService extends ChannelStartupService {
await this.openaiService.sendOpenai(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
pushName,
messageRaw,
);
}
@ -960,6 +961,7 @@ export class BusinessStartupService extends ChannelStartupService {
await this.openaiService.sendOpenai(
{ instanceName: this.instance.name, instanceId: this.instanceId },
messageRaw.key.remoteJid,
messageRaw.pushName,
messageRaw,
);
}

View File

@ -0,0 +1,45 @@
function normalizeString(str: string): string {
return str
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
export function advancedOperatorsSearch(data: string, query: string): boolean {
const filters = query.split(' ').reduce((acc: Record<string, string[]>, filter) => {
const [operator, ...values] = filter.split(':');
const value = values.join(':');
if (!acc[operator]) {
acc[operator] = [];
}
acc[operator].push(value);
return acc;
}, {});
const normalizedItem = normalizeString(data);
return Object.entries(filters).every(([operator, values]) => {
return values.some((val) => {
const subValues = val.split(',');
return subValues.every((subVal) => {
const normalizedSubVal = normalizeString(subVal);
switch (operator.toLowerCase()) {
case 'contains':
return normalizedItem.includes(normalizedSubVal);
case 'notcontains':
return !normalizedItem.includes(normalizedSubVal);
case 'startswith':
return normalizedItem.startsWith(normalizedSubVal);
case 'endswith':
return normalizedItem.endsWith(normalizedSubVal);
case 'exact':
return normalizedItem === normalizedSubVal;
default:
return false;
}
});
});
});
}