Merge pull request #1508 from gomessguii/develop

refactor: improve chatbot integrations
This commit is contained in:
Davidson Gomes 2025-05-27 18:03:44 -03:00 committed by GitHub
commit 3500fbe27f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 887 additions and 1884 deletions

File diff suppressed because one or more lines are too long

381
manager/dist/assets/index-D-oOjDYe.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="https://evolution-api.com/files/evo/favicon.svg" /> <link rel="icon" type="image/png" href="https://evolution-api.com/files/evo/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Evolution Manager</title> <title>Evolution Manager</title>
<script type="module" crossorigin src="/assets/index-mxi8bQ4k.js"></script> <script type="module" crossorigin src="/assets/index-D-oOjDYe.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DNOCacL_.css"> <link rel="stylesheet" crossorigin href="/assets/index-CXH2BdD4.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -165,7 +165,7 @@ export class EvolutionStartupService extends ChannelStartupService {
openAiDefaultSettings.speechToText && openAiDefaultSettings.speechToText &&
received?.message?.audioMessage received?.message?.audioMessage
) { ) {
messageRaw.message.speechToText = await this.openaiService.speechToText(received); messageRaw.message.speechToText = await this.openaiService.speechToText(received, this);
} }
} }

View File

@ -1298,7 +1298,7 @@ export class BaileysStartupService extends ChannelStartupService {
}); });
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) {
messageRaw.message.speechToText = await this.openaiService.speechToText(received); messageRaw.message.speechToText = await this.openaiService.speechToText(received, this);
} }
} }
@ -2324,7 +2324,7 @@ export class BaileysStartupService extends ChannelStartupService {
}); });
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) { if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) {
messageRaw.message.speechToText = await this.openaiService.speechToText(messageRaw); messageRaw.message.speechToText = await this.openaiService.speechToText(messageRaw, this);
} }
} }

View File

@ -353,20 +353,25 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
? pushName ? pushName
: null; : null;
session = ( const sessionResult = await this.createNewSession(
await this.createNewSession( {
{ instanceName: instance.instanceName,
instanceName: instance.instanceName, instanceId: instance.instanceId,
instanceId: instance.instanceId, },
}, {
{ remoteJid,
remoteJid, pushName: pushNameValue,
pushName: pushNameValue, botId: (bot as any).id,
botId: (bot as any).id, },
}, this.getBotType(),
this.getBotType(), );
)
)?.session; if (!sessionResult || !sessionResult.session) {
this.logger.error('Failed to create new session');
return;
}
session = sessionResult.session;
} }
// Update session status to opened // Update session status to opened

View File

@ -80,7 +80,7 @@ export class DifyController extends BaseChatbotController<DifyModel, DifyDto> {
} }
} }
// Bots // Override createBot to add Dify-specific validation
public async createBot(instance: InstanceDto, data: DifyDto) { public async createBot(instance: InstanceDto, data: DifyDto) {
if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled'); if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled');
@ -92,7 +92,7 @@ export class DifyController extends BaseChatbotController<DifyModel, DifyDto> {
}) })
.then((instance) => instance.id); .then((instance) => instance.id);
// Check for Dify-specific duplicate // Dify-specific duplicate check
const checkDuplicate = await this.botRepository.findFirst({ const checkDuplicate = await this.botRepository.findFirst({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
@ -106,62 +106,10 @@ export class DifyController extends BaseChatbotController<DifyModel, DifyDto> {
throw new Error('Dify already exists'); throw new Error('Dify already exists');
} }
// Let the base class handle the rest of the bot creation process // Let the base class handle the rest
return super.createBot(instance, data); return super.createBot(instance, data);
} }
public async findBot(instance: InstanceDto) {
if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bots = await this.botRepository.findMany({
where: {
instanceId: instanceId,
},
});
if (!bots.length) {
return null;
}
return bots;
}
public async fetchBot(instance: InstanceDto, botId: string) {
if (!this.integrationEnabled) throw new BadRequestException('Dify is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bot = await this.botRepository.findFirst({
where: {
id: botId,
},
});
if (!bot) {
throw new Error('Dify not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Dify not found');
}
return bot;
}
// Process Dify-specific bot logic // Process Dify-specific bot logic
protected async processBot( protected async processBot(
instance: any, instance: any,
@ -173,6 +121,6 @@ export class DifyController extends BaseChatbotController<DifyModel, DifyDto> {
pushName?: string, pushName?: string,
msg?: any, msg?: any,
) { ) {
this.difyService.process(instance, remoteJid, bot, session, settings, content, pushName, msg); await this.difyService.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
} }
} }

View File

@ -3,12 +3,11 @@ import { $Enums } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class DifyDto extends BaseChatbotDto { export class DifyDto extends BaseChatbotDto {
// Dify specific fields
botType?: $Enums.DifyBotType; botType?: $Enums.DifyBotType;
apiUrl?: string; apiUrl?: string;
apiKey?: string; apiKey?: string;
} }
export class DifySettingDto extends BaseChatbotSettingDto { export class DifySettingDto extends BaseChatbotSettingDto {
// Dify specific fields difyIdFallback?: string;
} }

View File

@ -1,10 +1,8 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { ConfigService, HttpServer } from '@config/env.config'; import { ConfigService, HttpServer } from '@config/env.config';
import { Dify, DifySetting, IntegrationSession } from '@prisma/client'; import { Dify, DifySetting, IntegrationSession } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
@ -15,8 +13,8 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
constructor( constructor(
waMonitor: WAMonitoringService, waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository, prismaRepository: PrismaRepository,
configService: ConfigService,
openaiService: OpenaiService, openaiService: OpenaiService,
) { ) {
super(waMonitor, prismaRepository, 'DifyService', configService); super(waMonitor, prismaRepository, 'DifyService', configService);
@ -30,10 +28,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
return 'dify'; return 'dify';
} }
public async createNewSession(instance: InstanceDto, data: any) {
return super.createNewSession(instance, data, 'dify');
}
protected async sendMessageToBot( protected async sendMessageToBot(
instance: any, instance: any,
session: IntegrationSession, session: IntegrationSession,
@ -43,10 +37,29 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
pushName: string, pushName: string,
content: string, content: string,
msg?: any, msg?: any,
) { ): Promise<void> {
try { try {
let endpoint: string = dify.apiUrl; let endpoint: string = dify.apiUrl;
if (!endpoint) {
this.logger.error('No Dify endpoint defined');
return;
}
// Handle audio messages - transcribe using OpenAI Whisper
let processedContent = content;
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[Dify] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg, instance);
if (transcription) {
processedContent = `[audio] ${transcription}`;
}
} catch (err) {
this.logger.error(`[Dify] Failed to transcribe audio: ${err}`);
}
}
if (dify.botType === 'chatBot') { if (dify.botType === 'chatBot') {
endpoint += '/chat-messages'; endpoint += '/chat-messages';
const payload: any = { const payload: any = {
@ -57,7 +70,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
serverUrl: this.configService.get<HttpServer>('SERVER').URL, serverUrl: this.configService.get<HttpServer>('SERVER').URL,
apiKey: instance.token, apiKey: instance.token,
}, },
query: content, query: processedContent,
response_mode: 'blocking', response_mode: 'blocking',
conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId, conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId,
user: remoteJid, user: remoteJid,
@ -66,7 +79,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
// Handle image messages // Handle image messages
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
const contentSplit = content.split('|'); const contentSplit = content.split('|');
payload.files = [ payload.files = [
{ {
type: 'image', type: 'image',
@ -77,22 +89,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
payload.query = contentSplit[2] || content; payload.query = contentSplit[2] || content;
} }
// Handle audio messages
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[Dify] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg);
if (transcription) {
payload.query = transcription;
} else {
payload.query = '[Audio message could not be transcribed]';
}
} catch (err) {
this.logger.error(`[Dify] Failed to transcribe audio: ${err}`);
payload.query = '[Audio message could not be transcribed]';
}
}
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
@ -110,7 +106,9 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
const message = response?.data?.answer; const message = response?.data?.answer;
const conversationId = response?.data?.conversation_id; const conversationId = response?.data?.conversation_id;
await this.sendMessageWhatsApp(instance, remoteJid, message, settings); if (message) {
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
}
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
@ -128,7 +126,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
endpoint += '/completion-messages'; endpoint += '/completion-messages';
const payload: any = { const payload: any = {
inputs: { inputs: {
query: content, query: processedContent,
pushName: pushName, pushName: pushName,
remoteJid: remoteJid, remoteJid: remoteJid,
instanceName: instance.instanceName, instanceName: instance.instanceName,
@ -143,7 +141,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
// Handle image messages // Handle image messages
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
const contentSplit = content.split('|'); const contentSplit = content.split('|');
payload.files = [ payload.files = [
{ {
type: 'image', type: 'image',
@ -154,22 +151,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
payload.inputs.query = contentSplit[2] || content; payload.inputs.query = contentSplit[2] || content;
} }
// Handle audio messages
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[Dify] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg);
if (transcription) {
payload.inputs.query = transcription;
} else {
payload.inputs.query = '[Audio message could not be transcribed]';
}
} catch (err) {
this.logger.error(`[Dify] Failed to transcribe audio: ${err}`);
payload.inputs.query = '[Audio message could not be transcribed]';
}
}
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
@ -187,7 +168,9 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
const message = response?.data?.answer; const message = response?.data?.answer;
const conversationId = response?.data?.conversation_id; const conversationId = response?.data?.conversation_id;
await this.sendMessageWhatsApp(instance, remoteJid, message, settings); if (message) {
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
}
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
@ -211,7 +194,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
serverUrl: this.configService.get<HttpServer>('SERVER').URL, serverUrl: this.configService.get<HttpServer>('SERVER').URL,
apiKey: instance.token, apiKey: instance.token,
}, },
query: content, query: processedContent,
response_mode: 'streaming', response_mode: 'streaming',
conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId, conversation_id: session.sessionId === remoteJid ? undefined : session.sessionId,
user: remoteJid, user: remoteJid,
@ -220,7 +203,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
// Handle image messages // Handle image messages
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
const contentSplit = content.split('|'); const contentSplit = content.split('|');
payload.files = [ payload.files = [
{ {
type: 'image', type: 'image',
@ -231,22 +213,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
payload.query = contentSplit[2] || content; payload.query = contentSplit[2] || content;
} }
// Handle audio messages
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[Dify] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg);
if (transcription) {
payload.query = transcription;
} else {
payload.query = '[Audio message could not be transcribed]';
}
} catch (err) {
this.logger.error(`[Dify] Failed to transcribe audio: ${err}`);
payload.query = '[Audio message could not be transcribed]';
}
}
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
@ -262,7 +228,6 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
let answer = ''; let answer = '';
const data = response.data.replaceAll('data: ', ''); const data = response.data.replaceAll('data: ', '');
const events = data.split('\n').filter((line) => line.trim() !== ''); const events = data.split('\n').filter((line) => line.trim() !== '');
for (const eventString of events) { for (const eventString of events) {
@ -280,7 +245,9 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
if (instance.integration === Integration.WHATSAPP_BAILEYS) if (instance.integration === Integration.WHATSAPP_BAILEYS)
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
await this.sendMessageWhatsApp(instance, remoteJid, answer, settings); if (answer) {
await this.sendMessageWhatsApp(instance, remoteJid, answer, settings);
}
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
@ -298,288 +265,4 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
return; return;
} }
} }
protected async sendMessageWhatsApp(instance: any, remoteJid: string, message: string, settings: DifySetting) {
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
let textBuffer = '';
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = linkRegex.exec(message)) !== null) {
const [fullMatch, exclamation, altText, url] = match;
const mediaType = this.getMediaType(url);
const beforeText = message.slice(lastIndex, match.index);
if (beforeText) {
textBuffer += beforeText;
}
if (mediaType) {
const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000;
const maxDelay = 20000;
if (textBuffer.trim()) {
if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: message,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
} else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: textBuffer,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
}
textBuffer = '';
if (mediaType === 'image') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'image',
media: url,
});
} else if (mediaType === 'video') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'video',
media: url,
});
} else if (mediaType === 'audio') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
mediatype: 'audio',
media: url,
});
} else if (mediaType === 'document') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'document',
media: url,
fileName: altText || 'file',
});
}
} else {
textBuffer += `[${altText}](${url})`;
}
lastIndex = match.index + fullMatch.length;
}
const remainingText = message.slice(lastIndex);
if (remainingText) {
textBuffer += remainingText;
}
if (textBuffer.trim()) {
const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000;
const maxDelay = 20000;
if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: message,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
} else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: textBuffer,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
}
}
protected async initNewSession(
instance: any,
remoteJid: string,
dify: Dify,
settings: DifySetting,
session: IntegrationSession,
content: string,
pushName?: string,
msg?: any,
) {
try {
await this.sendMessageToBot(instance, session, settings, dify, remoteJid, pushName || '', content, msg);
} catch (error) {
this.logger.error(error);
return;
}
}
public async process(
instance: any,
remoteJid: string,
dify: Dify,
session: IntegrationSession,
settings: DifySetting,
content: string,
pushName?: string,
msg?: any,
) {
try {
// Handle keyword finish
if (settings?.keywordFinish?.includes(content.toLowerCase())) {
if (settings?.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.delete({
where: {
id: session.id,
},
});
}
await sendTelemetry('/dify/session/finish');
return;
}
// If session is new or doesn't exist
if (!session) {
const data = {
remoteJid,
pushName,
botId: dify.id,
};
const createSession = await this.createNewSession(
{ instanceName: instance.instanceName, instanceId: instance.instanceId },
data,
);
await this.initNewSession(instance, remoteJid, dify, settings, createSession.session, content, pushName, msg);
await sendTelemetry('/dify/session/start');
return;
}
// If session exists but is paused
if (session.status === 'paused') {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
},
});
return;
}
// Regular message for ongoing session
await this.sendMessageToBot(instance, session, settings, dify, remoteJid, pushName || '', content, msg);
} catch (error) {
this.logger.error(error);
return;
}
}
} }

View File

@ -77,7 +77,7 @@ export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto>
} }
} }
// Bots // Override createBot to add EvoAI-specific validation
public async createBot(instance: InstanceDto, data: EvoaiDto) { public async createBot(instance: InstanceDto, data: EvoaiDto) {
if (!this.integrationEnabled) throw new BadRequestException('Evoai is disabled'); if (!this.integrationEnabled) throw new BadRequestException('Evoai is disabled');
@ -89,6 +89,7 @@ export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto>
}) })
.then((instance) => instance.id); .then((instance) => instance.id);
// EvoAI-specific duplicate check
const checkDuplicate = await this.botRepository.findFirst({ const checkDuplicate = await this.botRepository.findFirst({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
@ -101,61 +102,10 @@ export class EvoaiController extends BaseChatbotController<EvoaiModel, EvoaiDto>
throw new Error('Evoai already exists'); throw new Error('Evoai already exists');
} }
// Let the base class handle the rest
return super.createBot(instance, data); return super.createBot(instance, data);
} }
public async findBot(instance: InstanceDto) {
if (!this.integrationEnabled) throw new BadRequestException('Evoai is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bots = await this.botRepository.findMany({
where: {
instanceId: instanceId,
},
});
if (!bots.length) {
return null;
}
return bots;
}
public async fetchBot(instance: InstanceDto, botId: string) {
if (!this.integrationEnabled) throw new BadRequestException('Evoai is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bot = await this.botRepository.findFirst({
where: {
id: botId,
},
});
if (!bot) {
throw new Error('Evoai not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Evoai not found');
}
return bot;
}
// Process Evoai-specific bot logic // Process Evoai-specific bot logic
protected async processBot( protected async processBot(
instance: any, instance: any,

View File

@ -1,11 +1,10 @@
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class EvoaiDto extends BaseChatbotDto { export class EvoaiDto extends BaseChatbotDto {
// Evoai specific fields
agentUrl?: string; agentUrl?: string;
apiKey?: string; apiKey?: string;
} }
export class EvoaiSettingDto extends BaseChatbotSettingDto { export class EvoaiSettingDto extends BaseChatbotSettingDto {
// Evoai specific fields evoaiIdFallback?: string;
} }

View File

@ -1,8 +1,7 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { ConfigService } from '@config/env.config'; import { ConfigService, HttpServer } from '@config/env.config';
import { Evoai, EvoaiSetting, IntegrationSession } from '@prisma/client'; import { Evoai, EvoaiSetting, IntegrationSession } from '@prisma/client';
import axios from 'axios'; import axios from 'axios';
import { downloadMediaMessage } from 'baileys'; import { downloadMediaMessage } from 'baileys';
@ -10,12 +9,18 @@ import { v4 as uuidv4 } from 'uuid';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
import { OpenaiService } from '../../openai/services/openai.service'; import { OpenaiService } from '../../openai/services/openai.service';
export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> { export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
private openaiService: OpenaiService; private openaiService: OpenaiService;
constructor(waMonitor: WAMonitoringService, prismaRepository: PrismaRepository, configService: ConfigService) { constructor(
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
openaiService: OpenaiService,
) {
super(waMonitor, prismaRepository, 'EvoaiService', configService); super(waMonitor, prismaRepository, 'EvoaiService', configService);
this.openaiService = new OpenaiService(waMonitor, prismaRepository, configService); this.openaiService = openaiService;
} }
/** /**
@ -25,52 +30,10 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
return 'evoai'; return 'evoai';
} }
public async createNewSession(instance: InstanceDto, data: any) {
return super.createNewSession(instance, data, 'evoai');
}
/** /**
* Override the process method to directly handle audio messages * Implement the abstract method to send message to EvoAI API
* Handles audio transcription, image processing, and complex JSON-RPC payload
*/ */
public async process(
instance: any,
remoteJid: string,
bot: Evoai,
session: IntegrationSession,
settings: EvoaiSetting,
content: string,
pushName?: string,
msg?: any,
): Promise<void> {
try {
this.logger.debug(`[EvoAI] Processing message with custom process method`);
let contentProcessed = content;
// Check if this is an audio message that we should try to transcribe
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[EvoAI] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg);
if (transcription) {
contentProcessed = transcription;
} else {
contentProcessed = '[Audio message could not be transcribed]';
}
} catch (err) {
this.logger.error(`[EvoAI] Failed to transcribe audio: ${err}`);
contentProcessed = '[Audio message could not be transcribed]';
}
}
// For non-audio messages or if transcription failed, proceed normally
return super.process(instance, remoteJid, bot, session, settings, contentProcessed, pushName, msg);
} catch (error) {
this.logger.error(`[EvoAI] Error in process: ${error}`);
return;
}
}
protected async sendMessageToBot( protected async sendMessageToBot(
instance: any, instance: any,
session: IntegrationSession, session: IntegrationSession,
@ -80,19 +43,40 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
pushName: string, pushName: string,
content: string, content: string,
msg?: any, msg?: any,
) { ): Promise<void> {
try { try {
this.logger.debug(`[EvoAI] Sending message to bot with content: ${content}`); this.logger.debug(`[EvoAI] Sending message to bot with content: ${content}`);
let processedContent = content;
// Handle audio messages - transcribe using OpenAI Whisper
if (this.isAudioMessage(content) && msg) {
try {
this.logger.debug(`[EvoAI] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg, instance);
if (transcription) {
processedContent = transcription;
}
} catch (err) {
this.logger.error(`[EvoAI] Failed to transcribe audio: ${err}`);
}
}
const endpoint: string = evoai.agentUrl; const endpoint: string = evoai.agentUrl;
if (!endpoint) {
this.logger.error('No EvoAI endpoint defined');
return;
}
const callId = `req-${uuidv4().substring(0, 8)}`; const callId = `req-${uuidv4().substring(0, 8)}`;
const messageId = uuidv4(); const messageId = msg?.key?.id || uuidv4();
// Prepare message parts // Prepare message parts
const parts = [ const parts = [
{ {
type: 'text', type: 'text',
text: content, text: processedContent,
}, },
]; ];
@ -130,6 +114,17 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
role: 'user', role: 'user',
parts, parts,
messageId: messageId, messageId: messageId,
metadata: {
messageKey: msg?.key,
},
},
metadata: {
remoteJid: remoteJid,
pushName: pushName,
fromMe: msg?.key?.fromMe,
instanceName: instance.instanceName,
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
apiKey: instance.token,
}, },
}, },
}; };
@ -177,22 +172,10 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
} }
this.logger.debug(`[EvoAI] Extracted message to send: ${message}`); this.logger.debug(`[EvoAI] Extracted message to send: ${message}`);
const conversationId = session.sessionId;
if (message) { if (message) {
await this.sendMessageWhatsApp(instance, remoteJid, message, settings); await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
} }
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
sessionId: conversationId,
},
});
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`[EvoAI] Error sending message: ${error?.response?.data ? JSON.stringify(error.response.data) : error}`, `[EvoAI] Error sending message: ${error?.response?.data ? JSON.stringify(error.response.data) : error}`,

View File

@ -71,7 +71,7 @@ export const evoaiSettingSchema: JSONSchema7 = {
keepOpen: { type: 'boolean' }, keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' }, debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } }, ignoreJids: { type: 'array', items: { type: 'string' } },
evoaiIdFallback: { type: 'string' }, botIdFallback: { type: 'string' },
splitMessages: { type: 'boolean' }, splitMessages: { type: 'boolean' },
timePerChar: { type: 'integer' }, timePerChar: { type: 'integer' },
}, },

View File

@ -1,38 +1,10 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class EvolutionBotDto extends BaseChatbotDto { export class EvolutionBotDto extends BaseChatbotDto {
apiUrl: string; apiUrl: string;
apiKey: string; apiKey: string;
enabled?: boolean;
expire?: number;
keywordFinish?: string | null;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: any;
splitMessages?: boolean;
timePerChar?: number;
} }
export class EvolutionBotSettingDto extends BaseChatbotSettingDto { export class EvolutionBotSettingDto extends BaseChatbotSettingDto {
expire?: number;
keywordFinish?: string | null;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
botIdFallback?: string; botIdFallback?: string;
ignoreJids?: any;
splitMessages?: boolean;
timePerChar?: number;
} }

View File

@ -2,7 +2,7 @@
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { Auth, ConfigService, HttpServer } from '@config/env.config'; import { ConfigService, HttpServer } from '@config/env.config';
import { EvolutionBot, EvolutionBotSetting, IntegrationSession } from '@prisma/client'; import { EvolutionBot, EvolutionBotSetting, IntegrationSession } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry'; import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
@ -15,8 +15,8 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
constructor( constructor(
waMonitor: WAMonitoringService, waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository, prismaRepository: PrismaRepository,
configService: ConfigService,
openaiService: OpenaiService, openaiService: OpenaiService,
) { ) {
super(waMonitor, prismaRepository, 'EvolutionBotService', configService); super(waMonitor, prismaRepository, 'EvolutionBotService', configService);
@ -62,15 +62,12 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
this.logger.debug(`[EvolutionBot] Downloading audio for Whisper transcription`); this.logger.debug(`[EvolutionBot] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg); const transcription = await this.openaiService.speechToText(msg, instance);
if (transcription) { if (transcription) {
payload.query = transcription; payload.query = transcription;
} else {
payload.query = '[Audio message could not be transcribed]';
} }
} catch (err) { } catch (err) {
this.logger.error(`[EvolutionBot] Failed to transcribe audio: ${err}`); this.logger.error(`[EvolutionBot] Failed to transcribe audio: ${err}`);
payload.query = '[Audio message could not be transcribed]';
} }
} }
@ -91,6 +88,13 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
const endpoint = bot.apiUrl;
if (!endpoint) {
this.logger.error('No Evolution Bot endpoint defined');
return;
}
let headers: any = { let headers: any = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}; };
@ -102,7 +106,7 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
}; };
} }
const response = await axios.post(bot.apiUrl, payload, { const response = await axios.post(endpoint, payload, {
headers, headers,
}); });

View File

@ -1,13 +1,16 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { configService, Flowise } from '@config/env.config';
import { Logger } from '@config/logger.config'; import { Logger } from '@config/logger.config';
import { Flowise, IntegrationSession } from '@prisma/client'; import { BadRequestException } from '@exceptions';
import { Flowise as FlowiseModel, IntegrationSession } from '@prisma/client';
import { BaseChatbotController } from '../../base-chatbot.controller'; import { BaseChatbotController } from '../../base-chatbot.controller';
import { FlowiseDto } from '../dto/flowise.dto'; import { FlowiseDto } from '../dto/flowise.dto';
import { FlowiseService } from '../services/flowise.service'; import { FlowiseService } from '../services/flowise.service';
export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto> { export class FlowiseController extends BaseChatbotController<FlowiseModel, FlowiseDto> {
constructor( constructor(
private readonly flowiseService: FlowiseService, private readonly flowiseService: FlowiseService,
prismaRepository: PrismaRepository, prismaRepository: PrismaRepository,
@ -23,14 +26,12 @@ export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto
public readonly logger = new Logger('FlowiseController'); public readonly logger = new Logger('FlowiseController');
protected readonly integrationName = 'Flowise'; protected readonly integrationName = 'Flowise';
integrationEnabled = true; // Set to true by default or use config value if available integrationEnabled = configService.get<Flowise>('FLOWISE').ENABLED;
botRepository: any; botRepository: any;
settingsRepository: any; settingsRepository: any;
sessionRepository: any; sessionRepository: any;
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
// Implementation of abstract methods required by BaseChatbotController
protected getFallbackBotId(settings: any): string | undefined { protected getFallbackBotId(settings: any): string | undefined {
return settings?.flowiseIdFallback; return settings?.flowiseIdFallback;
} }
@ -50,7 +51,6 @@ export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto
}; };
} }
// Implementation for bot-specific updates
protected getAdditionalUpdateFields(data: FlowiseDto): Record<string, any> { protected getAdditionalUpdateFields(data: FlowiseDto): Record<string, any> {
return { return {
apiUrl: data.apiUrl, apiUrl: data.apiUrl,
@ -58,13 +58,10 @@ export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto
}; };
} }
// Implementation for bot-specific duplicate validation on update
protected async validateNoDuplicatesOnUpdate(botId: string, instanceId: string, data: FlowiseDto): Promise<void> { protected async validateNoDuplicatesOnUpdate(botId: string, instanceId: string, data: FlowiseDto): Promise<void> {
const checkDuplicate = await this.botRepository.findFirst({ const checkDuplicate = await this.botRepository.findFirst({
where: { where: {
id: { id: { not: botId },
not: botId,
},
instanceId: instanceId, instanceId: instanceId,
apiUrl: data.apiUrl, apiUrl: data.apiUrl,
apiKey: data.apiKey, apiKey: data.apiKey,
@ -76,16 +73,46 @@ export class FlowiseController extends BaseChatbotController<Flowise, FlowiseDto
} }
} }
// Process bot-specific logic // Process Flowise-specific bot logic
protected async processBot( protected async processBot(
instance: any, instance: any,
remoteJid: string, remoteJid: string,
bot: Flowise, bot: FlowiseModel,
session: IntegrationSession, session: IntegrationSession,
settings: any, settings: any,
content: string, content: string,
pushName?: string, pushName?: string,
msg?: any,
) { ) {
await this.flowiseService.process(instance, remoteJid, bot, session, settings, content, pushName); await this.flowiseService.processBot(instance, remoteJid, bot, session, settings, content, pushName, msg);
}
// Override createBot to add module availability check and Flowise-specific validation
public async createBot(instance: InstanceDto, data: FlowiseDto) {
if (!this.integrationEnabled) throw new BadRequestException('Flowise is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
// Flowise-specific duplicate check
const checkDuplicate = await this.botRepository.findFirst({
where: {
instanceId: instanceId,
apiUrl: data.apiUrl,
apiKey: data.apiKey,
},
});
if (checkDuplicate) {
throw new Error('Flowise already exists');
}
// Let the base class handle the rest
return super.createBot(instance, data);
} }
} }

View File

@ -1,39 +1,10 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class FlowiseDto extends BaseChatbotDto { export class FlowiseDto extends BaseChatbotDto {
apiUrl: string; apiUrl: string;
apiKey: string; apiKey?: string;
description: string;
keywordFinish?: string | null;
triggerType: TriggerType;
enabled?: boolean;
expire?: number;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: any;
splitMessages?: boolean;
timePerChar?: number;
} }
export class FlowiseSettingDto extends BaseChatbotSettingDto { export class FlowiseSettingDto extends BaseChatbotSettingDto {
expire?: number;
keywordFinish?: string | null;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
flowiseIdFallback?: string; flowiseIdFallback?: string;
ignoreJids?: any;
splitMessages?: boolean;
timePerChar?: number;
} }

View File

@ -2,133 +2,135 @@
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { Auth, ConfigService, HttpServer } from '@config/env.config'; import { ConfigService, HttpServer } from '@config/env.config';
import { Flowise, FlowiseSetting, IntegrationSession } from '@prisma/client'; import { Flowise as FlowiseModel, IntegrationSession } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
import { OpenaiService } from '../../openai/services/openai.service'; import { OpenaiService } from '../../openai/services/openai.service';
export class FlowiseService extends BaseChatbotService<Flowise, FlowiseSetting> { export class FlowiseService extends BaseChatbotService<FlowiseModel> {
private openaiService: OpenaiService; private openaiService: OpenaiService;
constructor( constructor(
waMonitor: WAMonitoringService, waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository, prismaRepository: PrismaRepository,
configService: ConfigService,
openaiService: OpenaiService, openaiService: OpenaiService,
) { ) {
super(waMonitor, prismaRepository, 'FlowiseService', configService); super(waMonitor, prismaRepository, 'FlowiseService', configService);
this.openaiService = openaiService; this.openaiService = openaiService;
} }
/** // Return the bot type for Flowise
* Get the bot type identifier
*/
protected getBotType(): string { protected getBotType(): string {
return 'flowise'; return 'flowise';
} }
/** // Process Flowise-specific bot logic
* Send a message to the Flowise API public async processBot(
*/ instance: any,
remoteJid: string,
bot: FlowiseModel,
session: IntegrationSession,
settings: any,
content: string,
pushName?: string,
msg?: any,
) {
await this.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
}
// Implement the abstract method to send message to Flowise API
protected async sendMessageToBot( protected async sendMessageToBot(
instance: any, instance: any,
session: IntegrationSession, session: IntegrationSession,
settings: FlowiseSetting, settings: any,
bot: Flowise, bot: FlowiseModel,
remoteJid: string, remoteJid: string,
pushName: string, pushName: string,
content: string, content: string,
msg?: any, msg?: any,
): Promise<void> { ): Promise<void> {
try { const payload: any = {
const payload: any = { question: content,
question: content, overrideConfig: {
overrideConfig: { sessionId: remoteJid,
sessionId: remoteJid, vars: {
vars: { remoteJid: remoteJid,
remoteJid: remoteJid, pushName: pushName,
pushName: pushName, instanceName: instance.instanceName,
instanceName: instance.instanceName, serverUrl: this.configService.get<HttpServer>('SERVER').URL,
serverUrl: this.configService.get<HttpServer>('SERVER').URL, apiKey: instance.token,
apiKey: instance.token,
},
}, },
}; },
};
if (this.isAudioMessage(content) && msg) { // Handle audio messages
try { if (this.isAudioMessage(content) && msg) {
this.logger.debug(`[EvolutionBot] Downloading audio for Whisper transcription`); try {
const transcription = await this.openaiService.speechToText(msg); this.logger.debug(`[Flowise] Downloading audio for Whisper transcription`);
if (transcription) { const transcription = await this.openaiService.speechToText(msg, instance);
payload.query = transcription; if (transcription) {
} else { payload.question = transcription;
payload.query = '[Audio message could not be transcribed]';
}
} catch (err) {
this.logger.error(`[EvolutionBot] Failed to transcribe audio: ${err}`);
payload.query = '[Audio message could not be transcribed]';
} }
} catch (err) {
this.logger.error(`[Flowise] Failed to transcribe audio: ${err}`);
} }
}
if (this.isImageMessage(content)) { if (this.isImageMessage(content)) {
const contentSplit = content.split('|'); const contentSplit = content.split('|');
payload.uploads = [ payload.uploads = [
{ {
data: contentSplit[1].split('?')[0], data: contentSplit[1].split('?')[0],
type: 'url', type: 'url',
name: 'Flowise.png', name: 'Flowise.png',
mime: 'image/png', mime: 'image/png',
}, },
]; ];
payload.question = contentSplit[2] || content; payload.question = contentSplit[2] || content;
} }
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.presenceSubscribe(remoteJid); await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid);
} }
let headers: any = { let headers: any = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
};
if (bot.apiKey) {
headers = {
...headers,
Authorization: `Bearer ${bot.apiKey}`,
}; };
}
if (bot.apiKey) { const endpoint = bot.apiUrl;
headers = {
...headers,
Authorization: `Bearer ${bot.apiKey}`,
};
}
const endpoint = bot.apiUrl; if (!endpoint) {
this.logger.error('No Flowise endpoint defined');
if (!endpoint) {
this.logger.error('No Flowise endpoint defined');
return;
}
const response = await axios.post(endpoint, payload, {
headers,
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
const message = response?.data?.text;
if (message) {
// Use the base class method to send the message to WhatsApp
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
}
// Send telemetry
sendTelemetry('/message/sendText');
} catch (error) {
this.logger.error(`Error in sendMessageToBot: ${error.message || JSON.stringify(error)}`);
return; return;
} }
const response = await axios.post(endpoint, payload, {
headers,
});
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
const message = response?.data?.text;
if (message) {
// Use the base class method to send the message to WhatsApp
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
}
} }
// The service is now complete with just the abstract method implementations
} }

View File

@ -40,6 +40,8 @@ export const flowiseSchema: JSONSchema7 = {
keepOpen: { type: 'boolean' }, keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' }, debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } }, ignoreJids: { type: 'array', items: { type: 'string' } },
splitMessages: { type: 'boolean' },
timePerChar: { type: 'integer' },
}, },
required: ['enabled', 'apiUrl', 'triggerType'], required: ['enabled', 'apiUrl', 'triggerType'],
...isNotEmpty('enabled', 'apiUrl', 'triggerType'), ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
@ -69,7 +71,9 @@ export const flowiseSettingSchema: JSONSchema7 = {
keepOpen: { type: 'boolean' }, keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' }, debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } }, ignoreJids: { type: 'array', items: { type: 'string' } },
botIdFallback: { type: 'string' }, flowiseIdFallback: { type: 'string' },
splitMessages: { type: 'boolean' },
timePerChar: { type: 'integer' },
}, },
required: [ required: [
'expire', 'expire',

View File

@ -110,58 +110,6 @@ export class N8nController extends BaseChatbotController<N8nModel, N8nDto> {
return super.createBot(instance, data); return super.createBot(instance, data);
} }
public async findBot(instance: InstanceDto) {
if (!this.integrationEnabled) throw new BadRequestException('N8n is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bots = await this.botRepository.findMany({
where: {
instanceId: instanceId,
},
});
if (!bots.length) {
return null;
}
return bots;
}
public async fetchBot(instance: InstanceDto, botId: string) {
if (!this.integrationEnabled) throw new BadRequestException('N8n is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bot = await this.botRepository.findFirst({
where: {
id: botId,
},
});
if (!bot) {
throw new Error('N8n not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('N8n not found');
}
return bot;
}
// Process N8n-specific bot logic // Process N8n-specific bot logic
protected async processBot( protected async processBot(
instance: any, instance: any,
@ -173,6 +121,7 @@ export class N8nController extends BaseChatbotController<N8nModel, N8nDto> {
pushName?: string, pushName?: string,
msg?: any, msg?: any,
) { ) {
this.n8nService.process(instance, remoteJid, bot, session, settings, content, pushName, msg); // Use the base class pattern instead of calling n8nService.process directly
await this.n8nService.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
} }
} }

View File

@ -1,5 +1,3 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class N8nDto extends BaseChatbotDto { export class N8nDto extends BaseChatbotDto {
@ -7,26 +5,10 @@ export class N8nDto extends BaseChatbotDto {
webhookUrl?: string; webhookUrl?: string;
basicAuthUser?: string; basicAuthUser?: string;
basicAuthPass?: string; basicAuthPass?: string;
// Advanced bot properties (copied from DifyDto style)
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
ignoreJids?: string[];
splitMessages?: boolean;
timePerChar?: number;
} }
export class N8nSettingDto extends BaseChatbotSettingDto { export class N8nSettingDto extends BaseChatbotSettingDto {
// N8n specific fields // N8n has no specific fields
} }
export class N8nMessageDto { export class N8nMessageDto {

View File

@ -1,14 +1,12 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { ConfigService, HttpServer } from '@config/env.config'; import { ConfigService, HttpServer } from '@config/env.config';
import { IntegrationSession, N8n, N8nSetting } from '@prisma/client'; import { IntegrationSession, N8n, N8nSetting } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
import { OpenaiService } from '../../openai/services/openai.service'; import { OpenaiService } from '../../openai/services/openai.service';
import { N8nDto } from '../dto/n8n.dto';
export class N8nService extends BaseChatbotService<N8n, N8nSetting> { export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
private openaiService: OpenaiService; private openaiService: OpenaiService;
@ -29,92 +27,6 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
return 'n8n'; return 'n8n';
} }
/**
* Create a new N8n bot for the given instance.
*/
public async createBot(instanceId: string, data: N8nDto) {
try {
return await this.prismaRepository.n8n.create({
data: {
enabled: data.enabled ?? true,
description: data.description,
webhookUrl: data.webhookUrl,
basicAuthUser: data.basicAuthUser,
basicAuthPass: data.basicAuthPass,
instanceId,
},
});
} catch (error) {
this.logger.error(error);
throw error;
}
}
/**
* Find all N8n bots for the given instance.
*/
public async findBots(instanceId: string) {
try {
return await this.prismaRepository.n8n.findMany({ where: { instanceId } });
} catch (error) {
this.logger.error(error);
throw error;
}
}
/**
* Fetch a specific N8n bot by ID and instance.
*/
public async fetchBot(instanceId: string, n8nId: string) {
try {
const bot = await this.prismaRepository.n8n.findFirst({ where: { id: n8nId } });
if (!bot || bot.instanceId !== instanceId) throw new Error('N8n bot not found');
return bot;
} catch (error) {
this.logger.error(error);
throw error;
}
}
/**
* Update a specific N8n bot.
*/
public async updateBot(instanceId: string, n8nId: string, data: N8nDto) {
try {
await this.fetchBot(instanceId, n8nId);
return await this.prismaRepository.n8n.update({
where: { id: n8nId },
data: {
enabled: data.enabled,
description: data.description,
webhookUrl: data.webhookUrl,
basicAuthUser: data.basicAuthUser,
basicAuthPass: data.basicAuthPass,
},
});
} catch (error) {
this.logger.error(error);
throw error;
}
}
/**
* Delete a specific N8n bot.
*/
public async deleteBot(instanceId: string, n8nId: string) {
try {
await this.fetchBot(instanceId, n8nId);
return await this.prismaRepository.n8n.delete({ where: { id: n8nId } });
} catch (error) {
this.logger.error(error);
throw error;
}
}
public async createNewSession(instance: InstanceDto, data: any) {
return super.createNewSession(instance, data, 'n8n');
}
protected async sendMessageToBot( protected async sendMessageToBot(
instance: any, instance: any,
session: IntegrationSession, session: IntegrationSession,
@ -126,6 +38,11 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
msg?: any, msg?: any,
) { ) {
try { try {
if (!session) {
this.logger.error('Session is null in sendMessageToBot');
return;
}
const endpoint: string = n8n.webhookUrl; const endpoint: string = n8n.webhookUrl;
const payload: any = { const payload: any = {
chatInput: content, chatInput: content,
@ -142,15 +59,12 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
this.logger.debug(`[N8n] Downloading audio for Whisper transcription`); this.logger.debug(`[N8n] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg); const transcription = await this.openaiService.speechToText(msg, instance);
if (transcription) { if (transcription) {
payload.chatInput = transcription; payload.chatInput = transcription;
} else {
payload.chatInput = '[Audio message could not be transcribed]';
} }
} catch (err) { } catch (err) {
this.logger.error(`[N8n] Failed to transcribe audio: ${err}`); this.logger.error(`[N8n] Failed to transcribe audio: ${err}`);
payload.chatInput = '[Audio message could not be transcribed]';
} }
} }
@ -161,7 +75,10 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
} }
const response = await axios.post(endpoint, payload, { headers }); const response = await axios.post(endpoint, payload, { headers });
const message = response?.data?.output || response?.data?.answer; const message = response?.data?.output || response?.data?.answer;
// Use base class method instead of custom implementation
await this.sendMessageWhatsApp(instance, remoteJid, message, settings); await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
await this.prismaRepository.integrationSession.update({ await this.prismaRepository.integrationSession.update({
where: { where: {
id: session.id, id: session.id,
@ -176,277 +93,4 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
return; return;
} }
} }
protected async sendMessageWhatsApp(instance: any, remoteJid: string, message: string, settings: N8nSetting) {
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
let textBuffer = '';
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = linkRegex.exec(message)) !== null) {
const [fullMatch, exclamation, altText, url] = match;
const mediaType = this.getMediaType(url);
const beforeText = message.slice(lastIndex, match.index).trim();
if (beforeText) {
textBuffer += beforeText;
}
if (mediaType) {
const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000;
const maxDelay = 20000;
if (textBuffer.trim()) {
if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: message,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
} else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: textBuffer,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
}
textBuffer = '';
if (mediaType === 'image') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'image',
media: url,
});
} else if (mediaType === 'video') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'video',
media: url,
});
} else if (mediaType === 'audio') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
mediatype: 'audio',
media: url,
});
} else if (mediaType === 'document') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
caption: exclamation === '!' ? undefined : altText,
mediatype: 'document',
media: url,
fileName: altText || 'file',
});
}
} else {
textBuffer += `[${altText}](${url})`;
}
lastIndex = match.index + fullMatch.length;
}
const remainingText = message.slice(lastIndex).trim();
if (remainingText) {
textBuffer += remainingText;
}
if (textBuffer.trim()) {
const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000;
const maxDelay = 20000;
if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: message,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
} else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.presenceSubscribe(remoteJid);
await instance.client.sendPresenceUpdate('composing', remoteJid);
}
await new Promise<void>((resolve) => {
setTimeout(async () => {
await instance.textMessage(
{
number: remoteJid.split('@')[0],
delay: settings?.delayMessage || 1000,
text: textBuffer,
},
false,
);
resolve();
}, delay);
});
if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid);
}
}
}
}
protected async initNewSession(
instance: any,
remoteJid: string,
n8n: N8n,
settings: N8nSetting,
session: IntegrationSession,
content: string,
pushName?: string,
msg?: any,
) {
try {
await this.sendMessageToBot(instance, session, settings, n8n, remoteJid, pushName || '', content, msg);
} catch (error) {
this.logger.error(error);
return;
}
}
public async process(
instance: any,
remoteJid: string,
n8n: N8n,
session: IntegrationSession,
settings: N8nSetting,
content: string,
pushName?: string,
msg?: any,
) {
try {
// Handle keyword finish
if (settings?.keywordFinish?.includes(content.toLowerCase())) {
if (settings?.keepOpen) {
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'closed',
},
});
} else {
await this.prismaRepository.integrationSession.delete({
where: {
id: session.id,
},
});
}
return;
}
// If session is new or doesn't exist
if (!session) {
const data = {
remoteJid,
pushName,
botId: n8n.id,
};
const createSession = await this.createNewSession(
{ instanceName: instance.instanceName, instanceId: instance.instanceId },
data,
);
await this.initNewSession(instance, remoteJid, n8n, settings, createSession.session, content, pushName, msg);
await sendTelemetry('/n8n/session/start');
return;
}
// If session exists but is paused
if (session.status === 'paused') {
return;
}
// Regular message for ongoing session
await this.sendMessageToBot(instance, session, settings, n8n, remoteJid, pushName || '', content, msg);
} catch (error) {
this.logger.error(error);
return;
}
}
} }

View File

@ -1,59 +1,116 @@
import { JSONSchema7 } from 'json-schema'; import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const n8nSchema: JSONSchema7 = { export const n8nSchema: JSONSchema7 = {
$id: v4(),
type: 'object', type: 'object',
properties: { properties: {
enabled: { type: 'boolean' }, enabled: { type: 'boolean' },
description: { type: 'string' }, description: { type: 'string' },
webhookUrl: { type: 'string', minLength: 1 }, webhookUrl: { type: 'string' },
basicAuthUser: { type: 'string' }, basicAuthUser: { type: 'string' },
basicAuthPass: { type: 'string' }, basicAuthPassword: { type: 'string' },
}, triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
required: ['enabled', 'webhookUrl'], triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
}; triggerValue: { type: 'string' },
expire: { type: 'integer' },
export const n8nMessageSchema: JSONSchema7 = {
type: 'object',
properties: {
chatInput: { type: 'string', minLength: 1 },
sessionId: { type: 'string', minLength: 1 },
},
required: ['chatInput', 'sessionId'],
};
export const n8nSettingSchema: JSONSchema7 = {
type: 'object',
properties: {
expire: { type: 'number' },
keywordFinish: { type: 'string' }, keywordFinish: { type: 'string' },
delayMessage: { type: 'number' }, delayMessage: { type: 'integer' },
unknownMessage: { type: 'string' }, unknownMessage: { type: 'string' },
listeningFromMe: { type: 'boolean' }, listeningFromMe: { type: 'boolean' },
stopBotFromMe: { type: 'boolean' }, stopBotFromMe: { type: 'boolean' },
keepOpen: { type: 'boolean' }, keepOpen: { type: 'boolean' },
debounceTime: { type: 'number' }, debounceTime: { type: 'integer' },
n8nIdFallback: { type: 'string' },
ignoreJids: { type: 'array', items: { type: 'string' } }, ignoreJids: { type: 'array', items: { type: 'string' } },
splitMessages: { type: 'boolean' }, splitMessages: { type: 'boolean' },
timePerChar: { type: 'number' }, timePerChar: { type: 'integer' },
}, },
required: [], required: ['enabled', 'webhookUrl', 'triggerType'],
...isNotEmpty('enabled', 'webhookUrl', 'triggerType'),
}; };
export const n8nStatusSchema: JSONSchema7 = { export const n8nStatusSchema: JSONSchema7 = {
$id: v4(),
type: 'object', type: 'object',
properties: { properties: {
remoteJid: { type: 'string' }, remoteJid: { type: 'string' },
status: { type: 'string', enum: ['opened', 'closed', 'delete', 'paused'] }, status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
}, },
required: ['remoteJid', 'status'], required: ['remoteJid', 'status'],
...isNotEmpty('remoteJid', 'status'),
};
export const n8nSettingSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
expire: { type: 'integer' },
keywordFinish: { type: 'string' },
delayMessage: { type: 'integer' },
unknownMessage: { type: 'string' },
listeningFromMe: { type: 'boolean' },
stopBotFromMe: { type: 'boolean' },
keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
botIdFallback: { type: 'string' },
splitMessages: { type: 'boolean' },
timePerChar: { type: 'integer' },
},
required: [
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
'splitMessages',
'timePerChar',
],
...isNotEmpty(
'expire',
'keywordFinish',
'delayMessage',
'unknownMessage',
'listeningFromMe',
'stopBotFromMe',
'keepOpen',
'debounceTime',
'ignoreJids',
'splitMessages',
'timePerChar',
),
}; };
export const n8nIgnoreJidSchema: JSONSchema7 = { export const n8nIgnoreJidSchema: JSONSchema7 = {
$id: v4(),
type: 'object', type: 'object',
properties: { properties: {
remoteJid: { type: 'string' }, remoteJid: { type: 'string' },
action: { type: 'string', enum: ['add', 'remove'] }, action: { type: 'string', enum: ['add', 'remove'] },
}, },
required: ['remoteJid', 'action'], required: ['remoteJid', 'action'],
...isNotEmpty('remoteJid', 'action'),
}; };

View File

@ -117,7 +117,7 @@ export class OpenaiController extends BaseChatbotController<OpenaiBot, OpenaiDto
} }
} }
// Bots // Override createBot to handle OpenAI-specific credential logic
public async createBot(instance: InstanceDto, data: OpenaiDto) { public async createBot(instance: InstanceDto, data: OpenaiDto) {
if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled'); if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled');
@ -206,58 +206,6 @@ export class OpenaiController extends BaseChatbotController<OpenaiBot, OpenaiDto
return super.createBot(instance, data); return super.createBot(instance, data);
} }
public async findBot(instance: InstanceDto) {
if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bots = await this.botRepository.findMany({
where: {
instanceId: instanceId,
},
});
if (!bots.length) {
return null;
}
return bots;
}
public async fetchBot(instance: InstanceDto, botId: string) {
if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled');
const instanceId = await this.prismaRepository.instance
.findFirst({
where: {
name: instance.instanceName,
},
})
.then((instance) => instance.id);
const bot = await this.botRepository.findFirst({
where: {
id: botId,
},
});
if (!bot) {
throw new Error('Openai Bot not found');
}
if (bot.instanceId !== instanceId) {
throw new Error('Openai Bot not found');
}
return bot;
}
// Process OpenAI-specific bot logic // Process OpenAI-specific bot logic
protected async processBot( protected async processBot(
instance: any, instance: any,
@ -284,8 +232,31 @@ export class OpenaiController extends BaseChatbotController<OpenaiBot, OpenaiDto
}) })
.then((instance) => instance.id); .then((instance) => instance.id);
if (!data.apiKey) throw new Error('API Key is required'); if (!data.apiKey) throw new BadRequestException('API Key is required');
if (!data.name) throw new Error('Name is required'); if (!data.name) throw new BadRequestException('Name is required');
// Check if API key already exists
const existingApiKey = await this.credsRepository.findFirst({
where: {
apiKey: data.apiKey,
},
});
if (existingApiKey) {
throw new BadRequestException('This API key is already registered. Please use a different API key.');
}
// Check if name already exists for this instance
const existingName = await this.credsRepository.findFirst({
where: {
name: data.name,
instanceId: instanceId,
},
});
if (existingName) {
throw new BadRequestException('This credential name is already in use. Please choose a different name.');
}
try { try {
const creds = await this.credsRepository.create({ const creds = await this.credsRepository.create({
@ -449,7 +420,7 @@ export class OpenaiController extends BaseChatbotController<OpenaiBot, OpenaiDto
} }
// Models - OpenAI specific functionality // Models - OpenAI specific functionality
public async getModels(instance: InstanceDto) { public async getModels(instance: InstanceDto, openaiCredsId?: string) {
if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled'); if (!this.integrationEnabled) throw new BadRequestException('Openai is disabled');
const instanceId = await this.prismaRepository.instance const instanceId = await this.prismaRepository.instance
@ -462,21 +433,40 @@ export class OpenaiController extends BaseChatbotController<OpenaiBot, OpenaiDto
if (!instanceId) throw new Error('Instance not found'); if (!instanceId) throw new Error('Instance not found');
const defaultSettings = await this.settingsRepository.findFirst({ let apiKey: string;
where: {
instanceId: instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (!defaultSettings) throw new Error('Settings not found'); if (openaiCredsId) {
// Use specific credential ID if provided
const creds = await this.credsRepository.findFirst({
where: {
id: openaiCredsId,
instanceId: instanceId, // Ensure the credential belongs to this instance
},
});
if (!defaultSettings.OpenaiCreds) if (!creds) throw new Error('OpenAI credentials not found for the provided ID');
throw new Error('OpenAI credentials not found. Please create credentials and associate them with the settings.');
const { apiKey } = defaultSettings.OpenaiCreds; apiKey = creds.apiKey;
} else {
// Use default credentials from settings if no ID provided
const defaultSettings = await this.settingsRepository.findFirst({
where: {
instanceId: instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (!defaultSettings) throw new Error('Settings not found');
if (!defaultSettings.OpenaiCreds)
throw new Error(
'OpenAI credentials not found. Please create credentials and associate them with the settings.',
);
apiKey = defaultSettings.OpenaiCreds.apiKey;
}
try { try {
this.client = new OpenAI({ apiKey }); this.client = new OpenAI({ apiKey });

View File

@ -153,7 +153,7 @@ export class OpenaiRouter extends RouterBroker {
request: req, request: req,
schema: instanceSchema, schema: instanceSchema,
ClassRef: InstanceDto, ClassRef: InstanceDto,
execute: (instance) => openaiController.getModels(instance), execute: (instance) => openaiController.getModels(instance, req.query.openaiCredsId as string),
}); });
res.status(HttpStatus.OK).json(response); res.status(HttpStatus.OK).json(response);

View File

@ -1,11 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { ConfigService, Language } from '@config/env.config'; import { ConfigService, Language, Openai as OpenaiConfig } from '@config/env.config';
import { Logger } from '@config/logger.config'; import { IntegrationSession, OpenaiBot, OpenaiSetting } from '@prisma/client';
import { IntegrationSession, OpenaiBot, OpenaiCreds, OpenaiSetting } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry'; import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { downloadMediaMessage } from 'baileys'; import { downloadMediaMessage } from 'baileys';
@ -33,13 +30,6 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
return 'openai'; return 'openai';
} }
/**
* Create a new session specific to OpenAI
*/
public async createNewSession(instance: InstanceDto, data: any) {
return super.createNewSession(instance, data, 'openai');
}
/** /**
* Initialize the OpenAI client with the provided API key * Initialize the OpenAI client with the provided API key
*/ */
@ -82,7 +72,7 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
this.initClient(creds.apiKey); this.initClient(creds.apiKey);
// Transcribe the audio // Transcribe the audio
const transcription = await this.speechToText(msg); const transcription = await this.speechToText(msg, instance);
if (transcription) { if (transcription) {
this.logger.log(`Audio transcribed: ${transcription}`); this.logger.log(`Audio transcribed: ${transcription}`);
@ -149,6 +139,7 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
const createSession = await this.createNewSession( const createSession = await this.createNewSession(
{ instanceName: instance.instanceName, instanceId: instance.instanceId }, { instanceName: instance.instanceName, instanceId: instance.instanceId },
data, data,
this.getBotType(),
); );
await this.initNewSession( await this.initNewSession(
@ -182,7 +173,7 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
} }
// Process with the appropriate API based on bot type // Process with the appropriate API based on bot type
await this.sendMessageToBot(instance, session, settings, openaiBot, remoteJid, pushName || '', content, msg); await this.sendMessageToBot(instance, session, settings, openaiBot, remoteJid, pushName || '', content);
} catch (error) { } catch (error) {
this.logger.error(`Error in process: ${error.message || JSON.stringify(error)}`); this.logger.error(`Error in process: ${error.message || JSON.stringify(error)}`);
return; return;
@ -200,7 +191,6 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
remoteJid: string, remoteJid: string,
pushName: string, pushName: string,
content: string, content: string,
msg?: any,
): Promise<void> { ): Promise<void> {
this.logger.log(`Sending message to bot for remoteJid: ${remoteJid}, bot type: ${openaiBot.botType}`); this.logger.log(`Sending message to bot for remoteJid: ${remoteJid}, bot type: ${openaiBot.botType}`);
@ -232,11 +222,10 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
pushName, pushName,
false, // Not fromMe false, // Not fromMe
content, content,
msg,
); );
} else { } else {
this.logger.log('Processing with ChatCompletion API'); this.logger.log('Processing with ChatCompletion API');
message = await this.processChatCompletionMessage(instance, openaiBot, remoteJid, content, msg); message = await this.processChatCompletionMessage(instance, openaiBot, remoteJid, content);
} }
this.logger.log(`Got response from OpenAI: ${message?.substring(0, 50)}${message?.length > 50 ? '...' : ''}`); this.logger.log(`Got response from OpenAI: ${message?.substring(0, 50)}${message?.length > 50 ? '...' : ''}`);
@ -279,7 +268,6 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
pushName: string, pushName: string,
fromMe: boolean, fromMe: boolean,
content: string, content: string,
msg?: any,
): Promise<string> { ): Promise<string> {
const messageData: any = { const messageData: any = {
role: fromMe ? 'assistant' : 'user', role: fromMe ? 'assistant' : 'user',
@ -388,7 +376,6 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
openaiBot: OpenaiBot, openaiBot: OpenaiBot,
remoteJid: string, remoteJid: string,
content: string, content: string,
msg?: any,
): Promise<string> { ): Promise<string> {
this.logger.log('Starting processChatCompletionMessage'); this.logger.log('Starting processChatCompletionMessage');
@ -650,159 +637,67 @@ export class OpenaiService extends BaseChatbotService<OpenaiBot, OpenaiSetting>
/** /**
* Implementation of speech-to-text transcription for audio messages * Implementation of speech-to-text transcription for audio messages
* This overrides the base class implementation with extra functionality
* Can be called directly with a message object or with an audio buffer
*/ */
public async speechToText(msgOrBuffer: any, updateMediaMessage?: any): Promise<string | null> { public async speechToText(msg: any, instance: any): Promise<string | null> {
try { const settings = await this.prismaRepository.openaiSetting.findFirst({
this.logger.log('Starting speechToText transcription'); where: {
instanceId: instance.instanceId,
},
});
// Handle direct calls with message object if (!settings) {
if (msgOrBuffer && (msgOrBuffer.key || msgOrBuffer.message)) { this.logger.error(`OpenAI settings not found. InstanceId: ${instance.instanceId}`);
this.logger.log('Processing message object for audio transcription');
const audioBuffer = await this.getAudioBufferFromMsg(msgOrBuffer, updateMediaMessage);
if (!audioBuffer) {
this.logger.error('Failed to get audio buffer from message');
return null;
}
this.logger.log(`Got audio buffer of size: ${audioBuffer.length} bytes`);
// Process the audio buffer with the base implementation
return this.processAudioTranscription(audioBuffer);
}
// Handle calls with a buffer directly (base implementation)
this.logger.log('Processing buffer directly for audio transcription');
return this.processAudioTranscription(msgOrBuffer);
} catch (err) {
this.logger.error(`Error in speechToText: ${err}`);
return null;
}
}
/**
* Helper method to process audio buffer for transcription
*/
private async processAudioTranscription(audioBuffer: Buffer): Promise<string | null> {
if (!this.configService) {
this.logger.error('ConfigService not available for speech-to-text transcription');
return null; return null;
} }
try { const creds = await this.prismaRepository.openaiCreds.findUnique({
// Use the initialized client's API key if available where: { id: settings.openaiCredsId },
let apiKey; });
if (this.client) { if (!creds) {
// Extract the API key from the initialized client if possible this.logger.error(`OpenAI credentials not found. CredsId: ${settings.openaiCredsId}`);
// OpenAI client doesn't expose the API key directly, so we need to use environment or config return null;
apiKey = this.configService.get<any>('OPENAI')?.API_KEY || process.env.OPENAI_API_KEY; }
} else {
this.logger.log('No OpenAI client initialized, using config API key');
apiKey = this.configService.get<any>('OPENAI')?.API_KEY || process.env.OPENAI_API_KEY;
}
if (!apiKey) { let audio: Buffer;
this.logger.error('No OpenAI API key set for Whisper transcription');
return null;
}
const lang = this.configService.get<Language>('LANGUAGE').includes('pt') if (msg.message.mediaUrl) {
? 'pt' audio = await axios.get(msg.message.mediaUrl, { responseType: 'arraybuffer' }).then((response) => {
: this.configService.get<Language>('LANGUAGE'); return Buffer.from(response.data, 'binary');
this.logger.log(`Sending audio for transcription with language: ${lang}`);
const formData = new FormData();
formData.append('file', audioBuffer, 'audio.ogg');
formData.append('model', 'whisper-1');
formData.append('language', lang);
this.logger.log('Making API request to OpenAI Whisper transcription');
const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, {
headers: {
...formData.getHeaders(),
Authorization: `Bearer ${apiKey}`,
},
}); });
} else if (msg.message.base64) {
this.logger.log(`Transcription completed: ${response?.data?.text || 'No text returned'}`); audio = Buffer.from(msg.message.base64, 'base64');
return response?.data?.text || null; } else {
} catch (err) { // Fallback for raw WhatsApp audio messages that need downloadMediaMessage
this.logger.error(`Whisper transcription failed: ${JSON.stringify(err.response?.data || err.message || err)}`); audio = await downloadMediaMessage(
return null; { key: msg.key, message: msg?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: instance,
},
);
} }
}
/** const lang = this.configService.get<Language>('LANGUAGE').includes('pt')
* Helper method to convert message to audio buffer ? 'pt'
*/ : this.configService.get<Language>('LANGUAGE');
private async getAudioBufferFromMsg(msg: any, updateMediaMessage: any): Promise<Buffer | null> {
try {
this.logger.log('Getting audio buffer from message');
this.logger.log(`Message type: ${msg.messageType}, has media URL: ${!!msg?.message?.mediaUrl}`);
let audio; const formData = new FormData();
formData.append('file', audio, 'audio.ogg');
formData.append('model', 'whisper-1');
formData.append('language', lang);
if (msg?.message?.mediaUrl) { const apiKey = creds?.apiKey || this.configService.get<OpenaiConfig>('OPENAI').API_KEY_GLOBAL;
this.logger.log(`Getting audio from media URL: ${msg.message.mediaUrl}`);
audio = await axios.get(msg.message.mediaUrl, { responseType: 'arraybuffer' }).then((response) => {
return Buffer.from(response.data, 'binary');
});
} else if (msg?.message?.audioMessage) {
// Handle WhatsApp audio messages
this.logger.log('Getting audio from audioMessage');
audio = await downloadMediaMessage(
{ key: msg.key, message: msg?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: updateMediaMessage,
},
);
} else if (msg?.message?.pttMessage) {
// Handle PTT voice messages
this.logger.log('Getting audio from pttMessage');
audio = await downloadMediaMessage(
{ key: msg.key, message: msg?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: updateMediaMessage,
},
);
} else {
this.logger.log('No recognized audio format found');
audio = await downloadMediaMessage(
{ key: msg.key, message: msg?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: updateMediaMessage,
},
);
}
if (audio) { const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, {
this.logger.log(`Successfully obtained audio buffer of size: ${audio.length} bytes`); headers: {
} else { 'Content-Type': 'multipart/form-data',
this.logger.error('Failed to obtain audio buffer'); Authorization: `Bearer ${apiKey}`,
} },
});
return audio; return response?.data?.text;
} catch (error) {
this.logger.error(`Error getting audio buffer: ${error.message || JSON.stringify(error)}`);
if (error.response) {
this.logger.error(`API response status: ${error.response.status}`);
this.logger.error(`API response data: ${JSON.stringify(error.response.data || {})}`);
}
return null;
}
} }
} }

View File

@ -90,23 +90,8 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
pushName?: string, pushName?: string,
msg?: any, msg?: any,
) { ) {
await this.typebotService.processTypebot( // Use the simplified service method that follows the base class pattern
instance, await this.typebotService.processTypebot(instance, remoteJid, bot, session, settings, content, pushName, msg);
remoteJid,
msg,
session,
bot,
bot.url,
settings.expire,
bot.typebot,
settings.keywordFinish,
settings.delayMessage,
settings.unknownMessage,
settings.listeningFromMe,
settings.stopBotFromMe,
settings.keepOpen,
content,
);
} }
// TypeBot specific method for starting a bot from API // TypeBot specific method for starting a bot from API
@ -117,7 +102,7 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
const instanceData = await this.prismaRepository.instance.findFirst({ const instanceData = await this.prismaRepository.instance.findFirst({
where: { where: {
name: instance.instanceName, id: instance.instanceId,
}, },
}); });
@ -226,22 +211,25 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
}, },
}); });
await this.typebotService.processTypebot( // Use the simplified service method instead of the complex one
instanceData, const settings = {
remoteJid,
null,
null,
findBot,
url,
expire, expire,
typebot,
keywordFinish, keywordFinish,
delayMessage, delayMessage,
unknownMessage, unknownMessage,
listeningFromMe, listeningFromMe,
stopBotFromMe, stopBotFromMe,
keepOpen, keepOpen,
};
await this.typebotService.processTypebot(
instanceData,
remoteJid,
findBot,
null, // session
settings,
'init', 'init',
null, // pushName
prefilledVariables, prefilledVariables,
); );
} else { } else {
@ -287,7 +275,7 @@ export class TypebotController extends BaseChatbotController<TypebotModel, Typeb
request.data.clientSideActions, request.data.clientSideActions,
); );
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { this.waMonitor.waInstances[instance.instanceId].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid, remoteJid: remoteJid,
url: url, url: url,
typebot: typebot, typebot: typebot,

View File

@ -1,5 +1,3 @@
import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class PrefilledVariables { export class PrefilledVariables {
@ -12,30 +10,8 @@ export class PrefilledVariables {
export class TypebotDto extends BaseChatbotDto { export class TypebotDto extends BaseChatbotDto {
url: string; url: string;
typebot: string; typebot: string;
description: string;
expire?: number;
keywordFinish?: string | null;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: any;
} }
export class TypebotSettingDto extends BaseChatbotSettingDto { export class TypebotSettingDto extends BaseChatbotSettingDto {
expire?: number;
keywordFinish?: string | null;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
typebotIdFallback?: string; typebotIdFallback?: string;
ignoreJids?: any;
} }

View File

@ -1,8 +1,7 @@
import { PrismaRepository } from '@api/repository/repository.service'; import { PrismaRepository } from '@api/repository/repository.service';
import { WAMonitoringService } from '@api/services/monitor.service'; import { WAMonitoringService } from '@api/services/monitor.service';
import { Auth, ConfigService, HttpServer, Typebot } from '@config/env.config'; import { Auth, ConfigService, HttpServer, Typebot } from '@config/env.config';
import { Instance, IntegrationSession, Message, Typebot as TypebotModel } from '@prisma/client'; import { IntegrationSession, Typebot as TypebotModel } from '@prisma/client';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
@ -83,15 +82,12 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
this.logger.debug(`[EvolutionBot] Downloading audio for Whisper transcription`); this.logger.debug(`[EvolutionBot] Downloading audio for Whisper transcription`);
const transcription = await this.openaiService.speechToText(msg); const transcription = await this.openaiService.speechToText(msg, instance);
if (transcription) { if (transcription) {
reqData.message = transcription; reqData.message = `[audio] ${transcription}`;
} else {
reqData.message = '[Audio message could not be transcribed]';
} }
} catch (err) { } catch (err) {
this.logger.error(`[EvolutionBot] Failed to transcribe audio: ${err}`); this.logger.error(`[EvolutionBot] Failed to transcribe audio: ${err}`);
reqData.message = '[Audio message could not be transcribed]';
} }
} }
@ -107,9 +103,6 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
response?.data?.input, response?.data?.input,
response?.data?.clientSideActions, response?.data?.clientSideActions,
); );
// Send telemetry data
sendTelemetry('/message/sendText');
} catch (error) { } catch (error) {
this.logger.error(`Error in sendMessageToBot for Typebot: ${error.message || JSON.stringify(error)}`); this.logger.error(`Error in sendMessageToBot for Typebot: ${error.message || JSON.stringify(error)}`);
} }
@ -526,44 +519,20 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
return { text, buttons: [] }; return { text, buttons: [] };
} }
} }
/** /**
* Main process method for handling Typebot messages * Simplified method that matches the base class pattern
* This is called directly from the controller * This should be the preferred way for the controller to call
*/ */
public async processTypebot( public async processTypebot(
instance: Instance, instance: any,
remoteJid: string, remoteJid: string,
msg: Message,
session: IntegrationSession,
bot: TypebotModel, bot: TypebotModel,
url: string, session: IntegrationSession,
expire: number, settings: any,
typebot: string,
keywordFinish: string,
delayMessage: number,
unknownMessage: string,
listeningFromMe: boolean,
stopBotFromMe: boolean,
keepOpen: boolean,
content: string, content: string,
prefilledVariables?: any, pushName?: string,
) { msg?: any,
try { ): Promise<void> {
const settings = { return this.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
expire,
keywordFinish,
delayMessage,
unknownMessage,
listeningFromMe,
stopBotFromMe,
keepOpen,
};
// Use the base class process method to handle the message
await this.process(instance, remoteJid, bot, session, settings, content, msg.pushName, prefilledVariables || msg);
} catch (error) {
this.logger.error(`Error in processTypebot: ${error}`);
}
} }
} }

View File

@ -123,19 +123,19 @@ export const openaiController = new OpenaiController(openaiService, prismaReposi
const typebotService = new TypebotService(waMonitor, configService, prismaRepository, openaiService); const typebotService = new TypebotService(waMonitor, configService, prismaRepository, openaiService);
export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor); export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor);
const difyService = new DifyService(waMonitor, configService, prismaRepository, openaiService); const difyService = new DifyService(waMonitor, prismaRepository, configService, openaiService);
export const difyController = new DifyController(difyService, prismaRepository, waMonitor); export const difyController = new DifyController(difyService, prismaRepository, waMonitor);
const evolutionBotService = new EvolutionBotService(waMonitor, configService, prismaRepository, openaiService); const evolutionBotService = new EvolutionBotService(waMonitor, prismaRepository, configService, openaiService);
export const evolutionBotController = new EvolutionBotController(evolutionBotService, prismaRepository, waMonitor); export const evolutionBotController = new EvolutionBotController(evolutionBotService, prismaRepository, waMonitor);
const flowiseService = new FlowiseService(waMonitor, configService, prismaRepository, openaiService); const flowiseService = new FlowiseService(waMonitor, prismaRepository, configService, openaiService);
export const flowiseController = new FlowiseController(flowiseService, prismaRepository, waMonitor); export const flowiseController = new FlowiseController(flowiseService, prismaRepository, waMonitor);
const n8nService = new N8nService(waMonitor, prismaRepository, configService, openaiService); const n8nService = new N8nService(waMonitor, prismaRepository, configService, openaiService);
export const n8nController = new N8nController(n8nService, prismaRepository, waMonitor); export const n8nController = new N8nController(n8nService, prismaRepository, waMonitor);
const evoaiService = new EvoaiService(waMonitor, prismaRepository, configService); const evoaiService = new EvoaiService(waMonitor, prismaRepository, configService, openaiService);
export const evoaiController = new EvoaiController(evoaiService, prismaRepository, waMonitor); export const evoaiController = new EvoaiController(evoaiService, prismaRepository, waMonitor);
logger.info('Module - ON'); logger.info('Module - ON');

View File

@ -49,7 +49,7 @@ export class ChannelStartupService {
public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository, this.openaiService); public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository, this.openaiService);
public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository, this.openaiService); public difyService = new DifyService(waMonitor, this.prismaRepository, this.configService, this.openaiService);
public setInstance(instance: InstanceDto) { public setInstance(instance: InstanceDto) {
this.logger.setInstance(instance.instanceName); this.logger.setInstance(instance.instanceName);

View File

@ -270,6 +270,7 @@ export type Openai = { ENABLED: boolean; API_KEY_GLOBAL?: string };
export type Dify = { ENABLED: boolean }; export type Dify = { ENABLED: boolean };
export type N8n = { ENABLED: boolean }; export type N8n = { ENABLED: boolean };
export type Evoai = { ENABLED: boolean }; export type Evoai = { ENABLED: boolean };
export type Flowise = { ENABLED: boolean };
export type S3 = { export type S3 = {
ACCESS_KEY: string; ACCESS_KEY: string;
@ -311,6 +312,7 @@ export interface Env {
DIFY: Dify; DIFY: Dify;
N8N: N8n; N8N: N8n;
EVOAI: Evoai; EVOAI: Evoai;
FLOWISE: Flowise;
CACHE: CacheConf; CACHE: CacheConf;
S3?: S3; S3?: S3;
AUTHENTICATION: Auth; AUTHENTICATION: Auth;
@ -626,6 +628,9 @@ export class ConfigService {
EVOAI: { EVOAI: {
ENABLED: process.env?.EVOAI_ENABLED === 'true', ENABLED: process.env?.EVOAI_ENABLED === 'true',
}, },
FLOWISE: {
ENABLED: process.env?.FLOWISE_ENABLED === 'true',
},
CACHE: { CACHE: {
REDIS: { REDIS: {
ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true', ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',

View File

@ -15,5 +15,6 @@ export default defineConfig({
}, },
loader: { loader: {
'.json': 'file', '.json': 'file',
'.yml': 'file',
}, },
}); });