From 95bd85b6e3cd9bec88ce28614c872388f9833b6b Mon Sep 17 00:00:00 2001 From: Guilherme Gomes Date: Tue, 27 May 2025 15:49:15 -0300 Subject: [PATCH] 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. --- .../flowise/controllers/flowise.controller.ts | 54 ++++-- .../chatbot/flowise/dto/flowise.dto.ts | 31 +-- .../flowise/services/flowise.service.ts | 178 +++++++++--------- .../flowise/validate/flowise.schema.ts | 6 +- src/api/server.module.ts | 2 +- src/config/env.config.ts | 5 + 6 files changed, 143 insertions(+), 133 deletions(-) diff --git a/src/api/integrations/chatbot/flowise/controllers/flowise.controller.ts b/src/api/integrations/chatbot/flowise/controllers/flowise.controller.ts index 6dc76b96..22db1f55 100644 --- a/src/api/integrations/chatbot/flowise/controllers/flowise.controller.ts +++ b/src/api/integrations/chatbot/flowise/controllers/flowise.controller.ts @@ -1,13 +1,16 @@ +import { InstanceDto } from '@api/dto/instance.dto'; import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; +import { configService, Flowise } from '@config/env.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 { FlowiseDto } from '../dto/flowise.dto'; import { FlowiseService } from '../services/flowise.service'; -export class FlowiseController extends BaseChatbotController { +export class FlowiseController extends BaseChatbotController { constructor( private readonly flowiseService: FlowiseService, prismaRepository: PrismaRepository, @@ -23,14 +26,12 @@ export class FlowiseController extends BaseChatbotController('FLOWISE').ENABLED; botRepository: any; settingsRepository: any; sessionRepository: any; userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {}; - // Implementation of abstract methods required by BaseChatbotController - protected getFallbackBotId(settings: any): string | undefined { return settings?.flowiseIdFallback; } @@ -50,7 +51,6 @@ export class FlowiseController extends BaseChatbotController { return { apiUrl: data.apiUrl, @@ -58,13 +58,10 @@ export class FlowiseController extends BaseChatbotController { const checkDuplicate = await this.botRepository.findFirst({ where: { - id: { - not: botId, - }, + id: { not: botId }, instanceId: instanceId, apiUrl: data.apiUrl, apiKey: data.apiKey, @@ -76,16 +73,47 @@ export class FlowiseController extends BaseChatbotController 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); } } diff --git a/src/api/integrations/chatbot/flowise/dto/flowise.dto.ts b/src/api/integrations/chatbot/flowise/dto/flowise.dto.ts index aa52e07d..4bbe1be3 100644 --- a/src/api/integrations/chatbot/flowise/dto/flowise.dto.ts +++ b/src/api/integrations/chatbot/flowise/dto/flowise.dto.ts @@ -1,39 +1,10 @@ -import { TriggerOperator, TriggerType } from '@prisma/client'; - import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; export class FlowiseDto extends BaseChatbotDto { apiUrl: 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; + apiKey?: string; } export class FlowiseSettingDto extends BaseChatbotSettingDto { - expire?: number; - keywordFinish?: string | null; - delayMessage?: number; - unknownMessage?: string; - listeningFromMe?: boolean; - stopBotFromMe?: boolean; - keepOpen?: boolean; - debounceTime?: number; flowiseIdFallback?: string; - ignoreJids?: any; - splitMessages?: boolean; - timePerChar?: number; } diff --git a/src/api/integrations/chatbot/flowise/services/flowise.service.ts b/src/api/integrations/chatbot/flowise/services/flowise.service.ts index 9b58ab0f..40dd2840 100644 --- a/src/api/integrations/chatbot/flowise/services/flowise.service.ts +++ b/src/api/integrations/chatbot/flowise/services/flowise.service.ts @@ -2,133 +2,135 @@ import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; import { Integration } from '@api/types/wa.types'; -import { Auth, ConfigService, HttpServer } from '@config/env.config'; -import { Flowise, FlowiseSetting, IntegrationSession } from '@prisma/client'; -import { sendTelemetry } from '@utils/sendTelemetry'; +import { ConfigService, HttpServer } from '@config/env.config'; +import { Flowise as FlowiseModel, IntegrationSession } from '@prisma/client'; import axios from 'axios'; import { BaseChatbotService } from '../../base-chatbot.service'; import { OpenaiService } from '../../openai/services/openai.service'; -export class FlowiseService extends BaseChatbotService { +export class FlowiseService extends BaseChatbotService { private openaiService: OpenaiService; + constructor( waMonitor: WAMonitoringService, - configService: ConfigService, prismaRepository: PrismaRepository, + configService: ConfigService, openaiService: OpenaiService, ) { super(waMonitor, prismaRepository, 'FlowiseService', configService); this.openaiService = openaiService; } - /** - * Get the bot type identifier - */ + // Return the bot type for Flowise protected getBotType(): string { return 'flowise'; } - /** - * Send a message to the Flowise API - */ + // Process Flowise-specific bot logic + 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( instance: any, session: IntegrationSession, - settings: FlowiseSetting, - bot: Flowise, + settings: any, + bot: FlowiseModel, remoteJid: string, pushName: string, content: string, msg?: any, ): Promise { - try { - const payload: any = { - question: content, - overrideConfig: { - sessionId: remoteJid, - vars: { - remoteJid: remoteJid, - pushName: pushName, - instanceName: instance.instanceName, - serverUrl: this.configService.get('SERVER').URL, - apiKey: instance.token, - }, + const payload: any = { + question: content, + overrideConfig: { + sessionId: remoteJid, + vars: { + remoteJid: remoteJid, + pushName: pushName, + instanceName: instance.instanceName, + serverUrl: this.configService.get('SERVER').URL, + apiKey: instance.token, }, - }; + }, + }; - if (this.isAudioMessage(content) && msg) { - try { - this.logger.debug(`[EvolutionBot] 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(`[EvolutionBot] Failed to transcribe audio: ${err}`); - payload.query = '[Audio message could not be transcribed]'; + // Handle audio messages + if (this.isAudioMessage(content) && msg) { + try { + this.logger.debug(`[Flowise] Downloading audio for Whisper transcription`); + const transcription = await this.openaiService.speechToText(msg, instance); + if (transcription) { + payload.question = transcription; } + } catch (err) { + this.logger.error(`[Flowise] Failed to transcribe audio: ${err}`); } + } - if (this.isImageMessage(content)) { - const contentSplit = content.split('|'); + if (this.isImageMessage(content)) { + const contentSplit = content.split('|'); - payload.uploads = [ - { - data: contentSplit[1].split('?')[0], - type: 'url', - name: 'Flowise.png', - mime: 'image/png', - }, - ]; - payload.question = contentSplit[2] || content; - } + payload.uploads = [ + { + data: contentSplit[1].split('?')[0], + type: 'url', + name: 'Flowise.png', + mime: 'image/png', + }, + ]; + payload.question = contentSplit[2] || content; + } - if (instance.integration === Integration.WHATSAPP_BAILEYS) { - await instance.client.presenceSubscribe(remoteJid); - await instance.client.sendPresenceUpdate('composing', remoteJid); - } + if (instance.integration === Integration.WHATSAPP_BAILEYS) { + await instance.client.presenceSubscribe(remoteJid); + await instance.client.sendPresenceUpdate('composing', remoteJid); + } - let headers: any = { - 'Content-Type': 'application/json', + let headers: any = { + 'Content-Type': 'application/json', + }; + + if (bot.apiKey) { + headers = { + ...headers, + Authorization: `Bearer ${bot.apiKey}`, }; + } - if (bot.apiKey) { - headers = { - ...headers, - Authorization: `Bearer ${bot.apiKey}`, - }; - } + const endpoint = bot.apiUrl; - const endpoint = bot.apiUrl; - - 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)}`); + 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); + } } + + // The service is now complete with just the abstract method implementations } diff --git a/src/api/integrations/chatbot/flowise/validate/flowise.schema.ts b/src/api/integrations/chatbot/flowise/validate/flowise.schema.ts index 83f9d1ea..3283d247 100644 --- a/src/api/integrations/chatbot/flowise/validate/flowise.schema.ts +++ b/src/api/integrations/chatbot/flowise/validate/flowise.schema.ts @@ -40,6 +40,8 @@ export const flowiseSchema: JSONSchema7 = { keepOpen: { type: 'boolean' }, debounceTime: { type: 'integer' }, ignoreJids: { type: 'array', items: { type: 'string' } }, + splitMessages: { type: 'boolean' }, + timePerChar: { type: 'integer' }, }, required: ['enabled', 'apiUrl', 'triggerType'], ...isNotEmpty('enabled', 'apiUrl', 'triggerType'), @@ -69,7 +71,9 @@ export const flowiseSettingSchema: JSONSchema7 = { keepOpen: { type: 'boolean' }, debounceTime: { type: 'integer' }, ignoreJids: { type: 'array', items: { type: 'string' } }, - botIdFallback: { type: 'string' }, + flowiseIdFallback: { type: 'string' }, + splitMessages: { type: 'boolean' }, + timePerChar: { type: 'integer' }, }, required: [ 'expire', diff --git a/src/api/server.module.ts b/src/api/server.module.ts index 42e2558f..81cca228 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -129,7 +129,7 @@ export const difyController = new DifyController(difyService, prismaRepository, const evolutionBotService = new EvolutionBotService(waMonitor, configService, prismaRepository, openaiService); 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); const n8nService = new N8nService(waMonitor, prismaRepository, configService, openaiService); diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 5a3b7bfa..8c253163 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -270,6 +270,7 @@ export type Openai = { ENABLED: boolean; API_KEY_GLOBAL?: string }; export type Dify = { ENABLED: boolean }; export type N8n = { ENABLED: boolean }; export type Evoai = { ENABLED: boolean }; +export type Flowise = { ENABLED: boolean }; export type S3 = { ACCESS_KEY: string; @@ -311,6 +312,7 @@ export interface Env { DIFY: Dify; N8N: N8n; EVOAI: Evoai; + FLOWISE: Flowise; CACHE: CacheConf; S3?: S3; AUTHENTICATION: Auth; @@ -626,6 +628,9 @@ export class ConfigService { EVOAI: { ENABLED: process.env?.EVOAI_ENABLED === 'true', }, + FLOWISE: { + ENABLED: process.env?.FLOWISE_ENABLED === 'true', + }, CACHE: { REDIS: { ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',