mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-13 15:14:49 -06:00
Merge pull request #1508 from gomessguii/develop
refactor: improve chatbot integrations
This commit is contained in:
commit
3500fbe27f
1
manager/dist/assets/index-CXH2BdD4.css
vendored
Normal file
1
manager/dist/assets/index-CXH2BdD4.css
vendored
Normal file
File diff suppressed because one or more lines are too long
381
manager/dist/assets/index-D-oOjDYe.js
vendored
Normal file
381
manager/dist/assets/index-D-oOjDYe.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
manager/dist/assets/index-DNOCacL_.css
vendored
1
manager/dist/assets/index-DNOCacL_.css
vendored
File diff suppressed because one or more lines are too long
381
manager/dist/assets/index-mxi8bQ4k.js
vendored
381
manager/dist/assets/index-mxi8bQ4k.js
vendored
File diff suppressed because one or more lines are too long
4
manager/dist/index.html
vendored
4
manager/dist/index.html
vendored
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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}`,
|
||||||
|
@ -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' },
|
||||||
},
|
},
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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'),
|
||||||
};
|
};
|
||||||
|
@ -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 });
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
@ -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);
|
||||||
|
@ -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',
|
||||||
|
@ -15,5 +15,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
loader: {
|
loader: {
|
||||||
'.json': 'file',
|
'.json': 'file',
|
||||||
|
'.yml': 'file',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user