import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; import { Integration } from '@api/types/wa.types'; import { ConfigService, HttpServer } from '@config/env.config'; import { Evoai, EvoaiSetting, IntegrationSession } from '@prisma/client'; import axios from 'axios'; import { downloadMediaMessage } from 'baileys'; import { v4 as uuidv4 } from 'uuid'; import { BaseChatbotService } from '../../base-chatbot.service'; import { OpenaiService } from '../../openai/services/openai.service'; export class EvoaiService extends BaseChatbotService { private openaiService: OpenaiService; constructor( waMonitor: WAMonitoringService, prismaRepository: PrismaRepository, configService: ConfigService, openaiService: OpenaiService, ) { super(waMonitor, prismaRepository, 'EvoaiService', configService); this.openaiService = openaiService; } /** * Return the bot type for EvoAI */ protected getBotType(): string { return 'evoai'; } /** * Implement the abstract method to send message to EvoAI API * Handles audio transcription, image processing, and complex JSON-RPC payload */ protected async sendMessageToBot( instance: any, session: IntegrationSession, settings: EvoaiSetting, evoai: Evoai, remoteJid: string, pushName: string, content: string, msg?: any, ): Promise { try { 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 = `[audio] ${transcription}`; } } catch (err) { this.logger.error(`[EvoAI] Failed to transcribe audio: ${err}`); } } const endpoint: string = evoai.agentUrl; if (!endpoint) { this.logger.error('No EvoAI endpoint defined'); return; } const callId = `req-${uuidv4().substring(0, 8)}`; const messageId = remoteJid.split('@')[0] || uuidv4(); // Use phone number as messageId // Prepare message parts const parts = [ { type: 'text', text: processedContent, }, ]; // Handle image message if present if (this.isImageMessage(content) && msg) { const contentSplit = content.split('|'); parts[0].text = contentSplit[2] || content; try { // Download the image const mediaBuffer = await downloadMediaMessage(msg, 'buffer', {}); const fileContent = Buffer.from(mediaBuffer).toString('base64'); const fileName = contentSplit[2] || `${msg.key?.id || 'image'}.jpg`; parts.push({ type: 'file', file: { name: fileName, mimeType: 'image/jpeg', bytes: fileContent, }, } as any); } catch (fileErr) { this.logger.error(`[EvoAI] Failed to process image: ${fileErr}`); } } const payload = { jsonrpc: '2.0', id: callId, method: 'message/send', params: { contextId: session.sessionId, message: { role: 'user', parts, messageId: messageId, metadata: { messageKey: msg?.key, }, }, metadata: { remoteJid: remoteJid, pushName: pushName, fromMe: msg?.key?.fromMe, instanceName: instance.instanceName, serverUrl: this.configService.get('SERVER').URL, apiKey: instance.token, }, }, }; this.logger.debug(`[EvoAI] Sending request to: ${endpoint}`); // Redact base64 file bytes from payload log const redactedPayload = JSON.parse(JSON.stringify(payload)); if (redactedPayload?.params?.message?.parts) { redactedPayload.params.message.parts = redactedPayload.params.message.parts.map((part) => { if (part.type === 'file' && part.file && part.file.bytes) { return { ...part, file: { ...part.file, bytes: '[base64 omitted]' } }; } return part; }); } this.logger.debug(`[EvoAI] Payload: ${JSON.stringify(redactedPayload)}`); if (instance.integration === Integration.WHATSAPP_BAILEYS) { await instance.client.presenceSubscribe(remoteJid); await instance.client.sendPresenceUpdate('composing', remoteJid); } const response = await axios.post(endpoint, payload, { headers: { 'x-api-key': evoai.apiKey, 'Content-Type': 'application/json', }, }); this.logger.debug(`[EvoAI] Response: ${JSON.stringify(response.data)}`); if (instance.integration === Integration.WHATSAPP_BAILEYS) await instance.client.sendPresenceUpdate('paused', remoteJid); let message = undefined; const result = response?.data?.result; // Extract message from artifacts array if (result?.artifacts && Array.isArray(result.artifacts) && result.artifacts.length > 0) { const artifact = result.artifacts[0]; if (artifact?.parts && Array.isArray(artifact.parts)) { const textPart = artifact.parts.find((p) => p.type === 'text' && p.text); if (textPart) message = textPart.text; } } this.logger.debug(`[EvoAI] Extracted message to send: ${message}`); if (message) { await this.sendMessageWhatsApp(instance, remoteJid, message, settings); } } catch (error) { this.logger.error( `[EvoAI] Error sending message: ${error?.response?.data ? JSON.stringify(error.response.data) : error}`, ); return; } } }