refactor: update Flowise integration for improved configuration and validation

This commit refines the Flowise integration by enhancing configuration management and validation logic. Key changes include:
- Reordered parameters in the FlowiseService constructor for consistency.
- Updated FlowiseController to utilize the configService for integration enablement checks.
- Simplified FlowiseDto and FlowiseSettingDto by removing unused properties.
- Enhanced validation logic in flowise.schema.ts to include new fields.
- Improved error handling in the createBot method to prevent duplicate entries.

These updates contribute to a more robust and maintainable Flowise integration.
This commit is contained in:
Guilherme Gomes 2025-05-27 15:49:15 -03:00
parent 64fc7a05ac
commit 95bd85b6e3
6 changed files with 143 additions and 133 deletions

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,47 @@ 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

@ -129,7 +129,7 @@ export const difyController = new DifyController(difyService, prismaRepository,
const evolutionBotService = new EvolutionBotService(waMonitor, configService, prismaRepository, openaiService); const evolutionBotService = new EvolutionBotService(waMonitor, configService, prismaRepository, 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);

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',