refactor(openai): improve service initialization and streamline audio transcription handling

- Updated OpenaiService and related classes to enhance the initialization process by ensuring the correct order of parameters.
- Simplified audio message handling by consolidating transcription logic and improving error handling.
- Refactored the OpenaiController to utilize the new structure, ensuring better integration with the base chatbot framework.
- Enhanced logging for better traceability during audio processing and API interactions.
This commit is contained in:
Guilherme Gomes 2025-05-21 12:16:12 -03:00
parent 69b4f1aa02
commit c30bae4c3a
18 changed files with 1127 additions and 1890 deletions

View File

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

View File

@ -501,16 +501,12 @@ export class BusinessStartupService extends ChannelStartupService {
openAiDefaultSettings.speechToText && openAiDefaultSettings.speechToText &&
audioMessage audioMessage
) { ) {
messageRaw.message.speechToText = await this.openaiService.speechToText( messageRaw.message.speechToText = await this.openaiService.speechToText({
openAiDefaultSettings.OpenaiCreds, message: {
{ mediaUrl: messageRaw.message.mediaUrl,
message: { ...messageRaw,
mediaUrl: messageRaw.message.mediaUrl,
...messageRaw,
},
}, },
() => {}, });
);
} }
} }

View File

@ -1298,11 +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( messageRaw.message.speechToText = await this.openaiService.speechToText(received);
openAiDefaultSettings.OpenaiCreds,
received,
this.client.updateMediaMessage,
);
} }
} }
@ -2297,11 +2293,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.message.speechToText = await this.openaiService.speechToText(messageRaw);
openAiDefaultSettings.OpenaiCreds,
messageRaw,
this.client.updateMediaMessage,
);
} }
} }

View File

@ -47,18 +47,21 @@ export interface BaseBotData {
[key: string]: any; [key: string]: any;
} }
export abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto> extends ChatbotController implements ChatbotControllerInterface { export abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>
extends ChatbotController
implements ChatbotControllerInterface
{
public readonly logger: Logger; public readonly logger: Logger;
integrationEnabled: boolean; integrationEnabled: boolean;
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 } } = {};
// Name of the integration, to be set by the derived class // Name of the integration, to be set by the derived class
protected abstract readonly integrationName: string; protected abstract readonly integrationName: string;
// Method to process bot-specific logic // Method to process bot-specific logic
protected abstract processBot( protected abstract processBot(
waInstance: any, waInstance: any,
@ -70,16 +73,13 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
pushName?: string, pushName?: string,
msg?: any, msg?: any,
): Promise<void>; ): Promise<void>;
// Method to get the fallback bot ID from settings // Method to get the fallback bot ID from settings
protected abstract getFallbackBotId(settings: any): string | undefined; protected abstract getFallbackBotId(settings: any): string | undefined;
constructor( constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
prismaRepository: PrismaRepository,
waMonitor: WAMonitoringService,
) {
super(prismaRepository, waMonitor); super(prismaRepository, waMonitor);
this.sessionRepository = this.prismaRepository.integrationSession; this.sessionRepository = this.prismaRepository.integrationSession;
} }
@ -161,7 +161,9 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
}); });
if (checkTriggerAll && data.triggerType === 'all') { if (checkTriggerAll && data.triggerType === 'all') {
throw new Error(`You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`); throw new Error(
`You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`,
);
} }
// Check for trigger keyword duplicates // Check for trigger keyword duplicates
@ -309,7 +311,7 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
// Get the name of the fallback field for this integration type // Get the name of the fallback field for this integration type
const fallbackFieldName = this.getFallbackFieldName(); const fallbackFieldName = this.getFallbackFieldName();
const settingsData = { const settingsData = {
expire: data.expire, expire: data.expire,
keywordFinish: data.keywordFinish, keywordFinish: data.keywordFinish,
@ -336,20 +338,25 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
// Map the specific fallback field to a generic 'fallbackId' in the response // Map the specific fallback field to a generic 'fallbackId' in the response
return { return {
...settings, ...settings,
fallbackId: settings[fallbackFieldName] fallbackId: settings[fallbackFieldName],
}; };
} else { } else {
const settings = await this.settingsRepository.create({ const settings = await this.settingsRepository.create({
data: { data: {
...settingsData, ...settingsData,
instanceId: instanceId, instanceId: instanceId,
Instance: {
connect: {
id: instanceId,
},
},
}, },
}); });
// Map the specific fallback field to a generic 'fallbackId' in the response // Map the specific fallback field to a generic 'fallbackId' in the response
return { return {
...settings, ...settings,
fallbackId: settings[fallbackFieldName] fallbackId: settings[fallbackFieldName],
}; };
} }
} catch (error) { } catch (error) {
@ -631,7 +638,9 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
}); });
if (checkTriggerAll) { if (checkTriggerAll) {
throw new Error(`You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`); throw new Error(
`You already have a ${this.integrationName} with an "All" trigger, you cannot have more bots while it is active`,
);
} }
} }
@ -779,15 +788,15 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return; if (this.checkIgnoreJids(settings?.ignoreJids, remoteJid)) return;
const session = await this.getSession(remoteJid, instance); const session = await this.getSession(remoteJid, instance);
const content = getConversationMessage(msg); const content = getConversationMessage(msg);
// Get integration type // Get integration type
const integrationType = this.getIntegrationType(); const integrationType = this.getIntegrationType();
// Find a bot for this message // Find a bot for this message
let findBot: any = await this.findBotTrigger(this.botRepository, content, instance, session); let findBot: any = await this.findBotTrigger(this.botRepository, content, instance, session);
// If no bot is found, try to use fallback // If no bot is found, try to use fallback
if (!findBot) { if (!findBot) {
const fallback = await this.settingsRepository.findFirst({ const fallback = await this.settingsRepository.findFirst({
@ -798,7 +807,7 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
// Get the fallback ID for this integration type // Get the fallback ID for this integration type
const fallbackId = this.getFallbackBotId(fallback); const fallbackId = this.getFallbackBotId(fallback);
if (fallbackId) { if (fallbackId) {
const findFallback = await this.botRepository.findFirst({ const findFallback = await this.botRepository.findFirst({
where: { where: {
@ -811,7 +820,7 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
return; return;
} }
} }
// If we still don't have a bot, return // If we still don't have a bot, return
if (!findBot) { if (!findBot) {
return; return;
@ -918,4 +927,4 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
this.logger.error(error); this.logger.error(error);
} }
} }
} }

View File

@ -39,4 +39,4 @@ export class BaseChatbotSettingDto {
splitMessages?: boolean; splitMessages?: boolean;
timePerChar?: number; timePerChar?: number;
fallbackId?: string; // Unified fallback ID field for all integrations fallbackId?: string; // Unified fallback ID field for all integrations
} }

View File

@ -1,10 +1,10 @@
import { InstanceDto } from '@api/dto/instance.dto'; 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 { ConfigService, Language } from '@config/env.config'; import { ConfigService, Language } from '@config/env.config';
import { Logger } from '@config/logger.config'; import { Logger } from '@config/logger.config';
import { IntegrationSession } from '@prisma/client'; import { IntegrationSession } from '@prisma/client';
import { Integration } from '@api/types/wa.types';
import axios from 'axios'; import axios from 'axios';
import FormData from 'form-data'; import FormData from 'form-data';
@ -17,7 +17,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
protected readonly waMonitor: WAMonitoringService; protected readonly waMonitor: WAMonitoringService;
protected readonly prismaRepository: PrismaRepository; protected readonly prismaRepository: PrismaRepository;
protected readonly configService?: ConfigService; protected readonly configService?: ConfigService;
constructor( constructor(
waMonitor: WAMonitoringService, waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository, prismaRepository: PrismaRepository,
@ -89,23 +89,23 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
this.logger.error('No OpenAI API key set for Whisper transcription'); this.logger.error('No OpenAI API key set for Whisper transcription');
return null; return null;
} }
const lang = this.configService.get<Language>('LANGUAGE').includes('pt') const lang = this.configService.get<Language>('LANGUAGE').includes('pt')
? 'pt' ? 'pt'
: this.configService.get<Language>('LANGUAGE'); : this.configService.get<Language>('LANGUAGE');
const formData = new FormData(); const formData = new FormData();
formData.append('file', audioBuffer, 'audio.ogg'); formData.append('file', audioBuffer, 'audio.ogg');
formData.append('model', 'whisper-1'); formData.append('model', 'whisper-1');
formData.append('language', lang); formData.append('language', lang);
const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, { const response = await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, {
headers: { headers: {
...formData.getHeaders(), ...formData.getHeaders(),
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
}); });
return response?.data?.text || null; return response?.data?.text || null;
} catch (err) { } catch (err) {
this.logger.error(`Whisper transcription failed: ${err}`); this.logger.error(`Whisper transcription failed: ${err}`);
@ -119,14 +119,16 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
public async createNewSession(instance: InstanceDto | any, data: any, type: string) { public async createNewSession(instance: InstanceDto | any, data: any, type: string) {
try { try {
// Extract pushName safely - if data.pushName is an object with a pushName property, use that // Extract pushName safely - if data.pushName is an object with a pushName property, use that
const pushNameValue = typeof data.pushName === 'object' && data.pushName?.pushName const pushNameValue =
? data.pushName.pushName typeof data.pushName === 'object' && data.pushName?.pushName
: (typeof data.pushName === 'string' ? data.pushName : null); ? data.pushName.pushName
: typeof data.pushName === 'string'
? data.pushName
: null;
// Extract remoteJid safely // Extract remoteJid safely
const remoteJidValue = typeof data.remoteJid === 'object' && data.remoteJid?.remoteJid const remoteJidValue =
? data.remoteJid.remoteJid typeof data.remoteJid === 'object' && data.remoteJid?.remoteJid ? data.remoteJid.remoteJid : data.remoteJid;
: data.remoteJid;
const session = await this.prismaRepository.integrationSession.create({ const session = await this.prismaRepository.integrationSession.create({
data: { data: {
@ -203,18 +205,18 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
* This handles common patterns like markdown links and formatting * This handles common patterns like markdown links and formatting
*/ */
protected async sendMessageWhatsApp( protected async sendMessageWhatsApp(
instance: any, instance: any,
remoteJid: string, remoteJid: string,
message: string, message: string,
settings: SettingsType settings: SettingsType,
): Promise<void> { ): Promise<void> {
if (!message) return; if (!message) return;
const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g; const linkRegex = /(!?)\[(.*?)\]\((.*?)\)/g;
let textBuffer = ''; let textBuffer = '';
let lastIndex = 0; let lastIndex = 0;
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
const splitMessages = (settings as any)?.splitMessages ?? false; const splitMessages = (settings as any)?.splitMessages ?? false;
const timePerChar = (settings as any)?.timePerChar ?? 0; const timePerChar = (settings as any)?.timePerChar ?? 0;
const minDelay = 1000; const minDelay = 1000;
@ -224,18 +226,18 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
const [fullMatch, exclamation, altText, url] = match; const [fullMatch, exclamation, altText, url] = match;
const mediaType = this.getMediaType(url); const mediaType = this.getMediaType(url);
const beforeText = message.slice(lastIndex, match.index); const beforeText = message.slice(lastIndex, match.index);
if (beforeText) { if (beforeText) {
textBuffer += beforeText; textBuffer += beforeText;
} }
if (mediaType) { if (mediaType) {
// Send accumulated text before sending media // Send accumulated text before sending media
if (textBuffer.trim()) { if (textBuffer.trim()) {
await this.sendFormattedText(instance, remoteJid, textBuffer.trim(), settings, splitMessages); await this.sendFormattedText(instance, remoteJid, textBuffer.trim(), settings, splitMessages);
textBuffer = ''; textBuffer = '';
} }
// Handle sending the media // Handle sending the media
try { try {
switch (mediaType) { switch (mediaType) {
@ -249,7 +251,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
break; break;
case 'video': case 'video':
await instance.mediaMessage({ await instance.mediaMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
delay: (settings as any)?.delayMessage || 1000, delay: (settings as any)?.delayMessage || 1000,
caption: altText, caption: altText,
media: url, media: url,
@ -280,48 +282,48 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
// It's a regular link, keep it in the text // It's a regular link, keep it in the text
textBuffer += `[${altText}](${url})`; textBuffer += `[${altText}](${url})`;
} }
lastIndex = match.index + fullMatch.length; lastIndex = match.index + fullMatch.length;
} }
// Add any remaining text after the last match // Add any remaining text after the last match
if (lastIndex < message.length) { if (lastIndex < message.length) {
textBuffer += message.slice(lastIndex); textBuffer += message.slice(lastIndex);
} }
// Send any remaining text // Send any remaining text
if (textBuffer.trim()) { if (textBuffer.trim()) {
await this.sendFormattedText(instance, remoteJid, textBuffer.trim(), settings, splitMessages); await this.sendFormattedText(instance, remoteJid, textBuffer.trim(), settings, splitMessages);
} }
} }
/** /**
* Helper method to send formatted text with proper typing indicators and delays * Helper method to send formatted text with proper typing indicators and delays
*/ */
private async sendFormattedText( private async sendFormattedText(
instance: any, instance: any,
remoteJid: string, remoteJid: string,
text: string, text: string,
settings: any, settings: any,
splitMessages: boolean splitMessages: boolean,
): Promise<void> { ): Promise<void> {
const timePerChar = settings?.timePerChar ?? 0; const timePerChar = settings?.timePerChar ?? 0;
const minDelay = 1000; const minDelay = 1000;
const maxDelay = 20000; const maxDelay = 20000;
if (splitMessages) { if (splitMessages) {
const multipleMessages = text.split('\n\n'); const multipleMessages = text.split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) { for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index]; const message = multipleMessages[index];
if (!message.trim()) continue; if (!message.trim()) continue;
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -335,19 +337,19 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} else { } else {
const delay = Math.min(Math.max(text.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(text.length * timePerChar, minDelay), maxDelay);
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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -361,7 +363,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === Integration.WHATSAPP_BAILEYS) { if (instance.integration === Integration.WHATSAPP_BAILEYS) {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
@ -385,15 +387,18 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
// Create a session if none exists // Create a session if none exists
if (!session) { if (!session) {
// Extract pushName properly - if it's an object with pushName property, use that // Extract pushName properly - if it's an object with pushName property, use that
const pushNameValue = typeof pushName === 'object' && pushName?.pushName const pushNameValue =
? pushName.pushName typeof pushName === 'object' && pushName?.pushName
: (typeof pushName === 'string' ? pushName : null); ? pushName.pushName
: typeof pushName === 'string'
? pushName
: null;
session = ( session = (
await this.createNewSession( await this.createNewSession(
{ {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instance.instanceId instanceId: instance.instanceId,
}, },
{ {
remoteJid, remoteJid,
@ -440,4 +445,4 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
content: string, content: string,
msg?: any, msg?: any,
): Promise<void>; ): Promise<void>;
} }

View File

@ -1,4 +1,3 @@
import { InstanceDto } from '@api/dto/instance.dto'; import { InstanceDto } from '@api/dto/instance.dto';
import { DifyDto } from '@api/integrations/chatbot/dify/dto/dify.dto'; import { DifyDto } from '@api/integrations/chatbot/dify/dto/dify.dto';
import { DifyService } from '@api/integrations/chatbot/dify/services/dify.service'; import { DifyService } from '@api/integrations/chatbot/dify/services/dify.service';

View File

@ -1,4 +1,5 @@
import { $Enums, TriggerOperator, TriggerType } from '@prisma/client'; import { $Enums, TriggerOperator, TriggerType } 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 {

View File

@ -2,21 +2,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 { Integration } from '@api/types/wa.types'; import { Integration } from '@api/types/wa.types';
import { Auth, 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 { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios'; import axios from 'axios';
import { downloadMediaMessage } from 'baileys'; import { downloadMediaMessage } from 'baileys';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
import { ConfigService, HttpServer } from '@config/env.config';
import { Auth } from '@config/env.config';
export class DifyService extends BaseChatbotService<Dify, DifySetting> { export class DifyService extends BaseChatbotService<Dify, DifySetting> {
constructor( constructor(waMonitor: WAMonitoringService, configService: ConfigService, prismaRepository: PrismaRepository) {
waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository,
) {
super(waMonitor, prismaRepository, 'DifyService', configService); super(waMonitor, prismaRepository, 'DifyService', configService);
} }
@ -73,7 +68,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
]; ];
payload.query = contentSplit[2] || content; payload.query = contentSplit[2] || content;
} }
// Handle audio messages // Handle audio messages
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
@ -151,7 +146,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
]; ];
payload.inputs.query = contentSplit[2] || content; payload.inputs.query = contentSplit[2] || content;
} }
// Handle audio messages // Handle audio messages
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
@ -229,7 +224,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
]; ];
payload.query = contentSplit[2] || content; payload.query = contentSplit[2] || content;
} }
// Handle audio messages // Handle audio messages
if (this.isAudioMessage(content) && msg) { if (this.isAudioMessage(content) && msg) {
try { try {
@ -554,16 +549,7 @@ export class DifyService extends BaseChatbotService<Dify, DifySetting> {
data, data,
); );
await this.initNewSession( await this.initNewSession(instance, remoteJid, dify, settings, createSession.session, content, pushName, msg);
instance,
remoteJid,
dify,
settings,
createSession.session,
content,
pushName,
msg,
);
await sendTelemetry('/dify/session/start'); await sendTelemetry('/dify/session/start');
return; return;

View File

@ -1,4 +1,5 @@
import { TriggerOperator, TriggerType } from '@prisma/client'; import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto'; import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class EvoaiDto extends BaseChatbotDto { export class EvoaiDto extends BaseChatbotDto {

View File

@ -11,11 +11,7 @@ import { v4 as uuidv4 } from 'uuid';
import { BaseChatbotService } from '../../base-chatbot.service'; import { BaseChatbotService } from '../../base-chatbot.service';
export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> { export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
constructor( constructor(waMonitor: WAMonitoringService, prismaRepository: PrismaRepository, configService: ConfigService) {
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(waMonitor, prismaRepository, 'EvoaiService', configService); super(waMonitor, prismaRepository, 'EvoaiService', configService);
} }
@ -45,24 +41,24 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
): Promise<void> { ): Promise<void> {
try { try {
this.logger.debug(`[EvoAI] Processing message with custom process method`); this.logger.debug(`[EvoAI] Processing message with custom process method`);
// Check if this is an audio message that we should try to transcribe // Check if this is an audio message that we should try to transcribe
if (msg?.messageType === 'audioMessage' && msg?.message?.audioMessage) { if (msg?.messageType === 'audioMessage' && msg?.message?.audioMessage) {
this.logger.debug(`[EvoAI] Detected audio message, attempting transcription`); this.logger.debug(`[EvoAI] Detected audio message, attempting transcription`);
try { try {
// Download the audio using the whole msg object // Download the audio using the whole msg object
const mediaBuffer = await downloadMediaMessage(msg, 'buffer', {}); const mediaBuffer = await downloadMediaMessage(msg, 'buffer', {});
this.logger.debug(`[EvoAI] Downloaded audio: ${mediaBuffer?.length || 0} bytes`); this.logger.debug(`[EvoAI] Downloaded audio: ${mediaBuffer?.length || 0} bytes`);
// Transcribe with OpenAI's Whisper // Transcribe with OpenAI's Whisper
const transcribedText = await this.speechToText(mediaBuffer); const transcribedText = await this.speechToText(mediaBuffer);
this.logger.debug(`[EvoAI] Transcription result: ${transcribedText || 'FAILED'}`); this.logger.debug(`[EvoAI] Transcription result: ${transcribedText || 'FAILED'}`);
if (transcribedText) { if (transcribedText) {
// Use the transcribed text instead of the original content // Use the transcribed text instead of the original content
this.logger.debug(`[EvoAI] Using transcribed text: ${transcribedText}`); this.logger.debug(`[EvoAI] Using transcribed text: ${transcribedText}`);
// Call the parent process method with the transcribed text // Call the parent process method with the transcribed text
return super.process(instance, remoteJid, bot, session, settings, transcribedText, pushName, msg); return super.process(instance, remoteJid, bot, session, settings, transcribedText, pushName, msg);
} }
@ -70,7 +66,7 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
this.logger.error(`[EvoAI] Audio transcription error: ${err}`); this.logger.error(`[EvoAI] Audio transcription error: ${err}`);
} }
} }
// For non-audio messages or if transcription failed, proceed normally // For non-audio messages or if transcription failed, proceed normally
return super.process(instance, remoteJid, bot, session, settings, content, pushName, msg); return super.process(instance, remoteJid, bot, session, settings, content, pushName, msg);
} catch (error) { } catch (error) {
@ -91,7 +87,7 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
) { ) {
try { try {
this.logger.debug(`[EvoAI] Sending message to bot with content: ${content}`); this.logger.debug(`[EvoAI] Sending message to bot with content: ${content}`);
const endpoint: string = evoai.agentUrl; const endpoint: string = evoai.agentUrl;
const callId = `call-${uuidv4()}`; const callId = `call-${uuidv4()}`;
const taskId = `task-${uuidv4()}`; const taskId = `task-${uuidv4()}`;
@ -108,13 +104,13 @@ export class EvoaiService extends BaseChatbotService<Evoai, EvoaiSetting> {
if (this.isImageMessage(content) && msg) { if (this.isImageMessage(content) && msg) {
const contentSplit = content.split('|'); const contentSplit = content.split('|');
parts[0].text = contentSplit[2] || content; parts[0].text = contentSplit[2] || content;
try { try {
// Download the image // Download the image
const mediaBuffer = await downloadMediaMessage(msg, 'buffer', {}); const mediaBuffer = await downloadMediaMessage(msg, 'buffer', {});
const fileContent = Buffer.from(mediaBuffer).toString('base64'); const fileContent = Buffer.from(mediaBuffer).toString('base64');
const fileName = contentSplit[2] || `${msg.key?.id || 'image'}.jpg`; const fileName = contentSplit[2] || `${msg.key?.id || 'image'}.jpg`;
parts.push({ parts.push({
type: 'file', type: 'file',
file: { file: {

View File

@ -1,4 +1,5 @@
import { TriggerOperator, TriggerType } from '@prisma/client'; 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 {

View File

@ -11,11 +11,7 @@ import { BaseChatbotService } from '../../base-chatbot.service';
import { N8nDto } from '../dto/n8n.dto'; import { N8nDto } from '../dto/n8n.dto';
export class N8nService extends BaseChatbotService<N8n, N8nSetting> { export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
constructor( constructor(waMonitor: WAMonitoringService, prismaRepository: PrismaRepository, configService: ConfigService) {
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(waMonitor, prismaRepository, 'N8nService', configService); super(waMonitor, prismaRepository, 'N8nService', configService);
} }
@ -194,34 +190,34 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
let textBuffer = ''; let textBuffer = '';
let lastIndex = 0; let lastIndex = 0;
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
while ((match = linkRegex.exec(message)) !== null) { while ((match = linkRegex.exec(message)) !== null) {
const [fullMatch, exclamation, altText, url] = match; const [fullMatch, exclamation, altText, url] = match;
const mediaType = this.getMediaType(url); const mediaType = this.getMediaType(url);
const beforeText = message.slice(lastIndex, match.index); const beforeText = message.slice(lastIndex, match.index);
if (beforeText) { if (beforeText) {
textBuffer += beforeText; textBuffer += beforeText;
} }
if (mediaType) { if (mediaType) {
const splitMessages = settings.splitMessages ?? false; const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0; const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000; const minDelay = 1000;
const maxDelay = 20000; const maxDelay = 20000;
if (textBuffer.trim()) { if (textBuffer.trim()) {
if (splitMessages) { if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n'); const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) { for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index]; const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -235,19 +231,19 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} else { } else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -261,15 +257,15 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} }
textBuffer = ''; textBuffer = '';
if (mediaType === 'image') { if (mediaType === 'image') {
await instance.mediaMessage({ await instance.mediaMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
@ -306,32 +302,32 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
} else { } else {
textBuffer += `[${altText}](${url})`; textBuffer += `[${altText}](${url})`;
} }
lastIndex = match.index + fullMatch.length; lastIndex = match.index + fullMatch.length;
} }
const remainingText = message.slice(lastIndex); const remainingText = message.slice(lastIndex);
if (remainingText) { if (remainingText) {
textBuffer += remainingText; textBuffer += remainingText;
} }
if (textBuffer.trim()) { if (textBuffer.trim()) {
const splitMessages = settings.splitMessages ?? false; const splitMessages = settings.splitMessages ?? false;
const timePerChar = settings.timePerChar ?? 0; const timePerChar = settings.timePerChar ?? 0;
const minDelay = 1000; const minDelay = 1000;
const maxDelay = 20000; const maxDelay = 20000;
if (splitMessages) { if (splitMessages) {
const multipleMessages = textBuffer.trim().split('\n\n'); const multipleMessages = textBuffer.trim().split('\n\n');
for (let index = 0; index < multipleMessages.length; index++) { for (let index = 0; index < multipleMessages.length; index++) {
const message = multipleMessages[index]; const message = multipleMessages[index];
const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(message.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -345,19 +341,19 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
} }
} else { } else {
const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay); const delay = Math.min(Math.max(textBuffer.length * timePerChar, minDelay), maxDelay);
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.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);
} }
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(async () => { setTimeout(async () => {
await instance.textMessage( await instance.textMessage(
@ -371,7 +367,7 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
resolve(); resolve();
}, delay); }, delay);
}); });
if (instance.integration === 'WHATSAPP_BAILEYS') { if (instance.integration === 'WHATSAPP_BAILEYS') {
await instance.client.sendPresenceUpdate('paused', remoteJid); await instance.client.sendPresenceUpdate('paused', remoteJid);
} }
@ -443,16 +439,7 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
data, data,
); );
await this.initNewSession( await this.initNewSession(instance, remoteJid, n8n, settings, createSession.session, content, pushName, msg);
instance,
remoteJid,
n8n,
settings,
createSession.session,
content,
pushName,
msg,
);
await sendTelemetry('/n8n/session/start'); await sendTelemetry('/n8n/session/start');
return; return;

View File

@ -1,15 +1,15 @@
import { TriggerOperator, TriggerType } from '@prisma/client'; import { TriggerOperator, TriggerType } from '@prisma/client';
import { BaseChatbotDto, BaseChatbotSettingDto } from '../../base-chatbot.dto';
export class OpenaiCredsDto { export class OpenaiCredsDto {
name: string; name: string;
apiKey: string; apiKey: string;
} }
export class OpenaiDto { export class OpenaiDto extends BaseChatbotDto {
enabled?: boolean;
description?: string;
openaiCredsId: string; openaiCredsId: string;
botType?: string; botType: string;
assistantId?: string; assistantId?: string;
functionUrl?: string; functionUrl?: string;
model?: string; model?: string;
@ -17,35 +17,10 @@ export class OpenaiDto {
assistantMessages?: string[]; assistantMessages?: string[];
userMessages?: string[]; userMessages?: string[];
maxTokens?: number; maxTokens?: number;
expire?: number;
keywordFinish?: string;
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 OpenaiSettingDto { export class OpenaiSettingDto extends BaseChatbotSettingDto {
openaiCredsId?: string; openaiCredsId?: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
openaiIdFallback?: string; openaiIdFallback?: string;
ignoreJids?: any;
speechToText?: boolean; speechToText?: boolean;
splitMessages?: boolean;
timePerChar?: number;
} }

File diff suppressed because it is too large Load Diff

View File

@ -119,7 +119,7 @@ export const baileysController = new BaileysController(waMonitor);
const typebotService = new TypebotService(waMonitor, configService, prismaRepository); const typebotService = new TypebotService(waMonitor, configService, prismaRepository);
export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor); export const typebotController = new TypebotController(typebotService, prismaRepository, waMonitor);
const openaiService = new OpenaiService(waMonitor, configService, prismaRepository); const openaiService = new OpenaiService(waMonitor, prismaRepository, configService);
export const openaiController = new OpenaiController(openaiService, prismaRepository, waMonitor); export const openaiController = new OpenaiController(openaiService, prismaRepository, waMonitor);
const difyService = new DifyService(waMonitor, configService, prismaRepository); const difyService = new DifyService(waMonitor, configService, prismaRepository);

View File

@ -47,7 +47,7 @@ export class ChannelStartupService {
public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository); public typebotService = new TypebotService(waMonitor, this.configService, this.prismaRepository);
public openaiService = new OpenaiService(waMonitor, this.configService, this.prismaRepository); public openaiService = new OpenaiService(waMonitor, this.prismaRepository, this.configService);
public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository); public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository);