diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index 8a74ed6d..7bc704e8 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -37,6 +37,7 @@ enum TriggerType { all keyword none + advanced } enum TriggerOperator { diff --git a/prisma/postgresql-migrations/20240817110155_add_trigger_type_advanced/migration.sql b/prisma/postgresql-migrations/20240817110155_add_trigger_type_advanced/migration.sql new file mode 100644 index 00000000..9d88fe37 --- /dev/null +++ b/prisma/postgresql-migrations/20240817110155_add_trigger_type_advanced/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "TriggerType" ADD VALUE 'advanced'; diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index dc9c7d14..0d155399 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -37,6 +37,7 @@ enum TriggerType { all keyword none + advanced } enum TriggerOperator { diff --git a/src/api/integrations/dify/services/dify.service.ts b/src/api/integrations/dify/services/dify.service.ts index 7a2cc6ba..b0d8464b 100644 --- a/src/api/integrations/dify/services/dify.service.ts +++ b/src/api/integrations/dify/services/dify.service.ts @@ -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: { diff --git a/src/api/integrations/dify/validate/dify.schema.ts b/src/api/integrations/dify/validate/dify.schema.ts index b3006f34..d244bbe4 100644 --- a/src/api/integrations/dify/validate/dify.schema.ts +++ b/src/api/integrations/dify/validate/dify.schema.ts @@ -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' }, diff --git a/src/api/integrations/openai/services/openai.service.ts b/src/api/integrations/openai/services/openai.service.ts index 45310162..964ff8a1 100644 --- a/src/api/integrations/openai/services/openai.service.ts +++ b/src/api/integrations/openai/services/openai.service.ts @@ -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, }, }); @@ -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: { diff --git a/src/api/integrations/openai/validate/openai.schema.ts b/src/api/integrations/openai/validate/openai.schema.ts index 4d782582..a4ccfe56 100644 --- a/src/api/integrations/openai/validate/openai.schema.ts +++ b/src/api/integrations/openai/validate/openai.schema.ts @@ -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' }, diff --git a/src/api/integrations/typebot/services/typebot.service.ts b/src/api/integrations/typebot/services/typebot.service.ts index 45addc26..4c7ab951 100644 --- a/src/api/integrations/typebot/services/typebot.service.ts +++ b/src/api/integrations/typebot/services/typebot.service.ts @@ -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: { diff --git a/src/api/integrations/typebot/validate/typebot.schema.ts b/src/api/integrations/typebot/validate/typebot.schema.ts index e473d016..02dc74e8 100644 --- a/src/api/integrations/typebot/validate/typebot.schema.ts +++ b/src/api/integrations/typebot/validate/typebot.schema.ts @@ -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' }, diff --git a/src/utils/advancedOperatorsSearch.ts b/src/utils/advancedOperatorsSearch.ts new file mode 100644 index 00000000..dc0ec5ce --- /dev/null +++ b/src/utils/advancedOperatorsSearch.ts @@ -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, 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; + } + }); + }); + }); +}