mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-13 15:14:49 -06:00
feat: Integration with Dify
Adds support for Dify integration, including new routes, services, and controllers. The configuration for Dify has been added to the environment file, and the necessary changes have been made to the `.env.example` file. Additionally, the WhatsApp Baileys service has been updated to handle Dify notifications. Modified files: - `.env.example` - `package.json` - `src/api/integrations/openai/services/openai.service.ts` - `src/api/routes/index.router.ts` - `src/api/server.module.ts` - `src/api/services/channel.service.ts` - `src/api/services/channels/whatsapp.baileys.service.ts` - `src/config/env.config.ts` - `src/validate/validate.schema.ts` Introduced files: - `src/api/integrations/dify/` - `src/api/integrations/dify/controllers/dify.controller.ts` - `src/api/integrations/dify/dto/dify.dto.ts` - `src/api/integrations/dify/routes/dify.router.ts` - `src/api/integrations/dify/services/dify.service.ts` - `src/api/integrations/dify/validate/dify.schema.ts`
This commit is contained in:
parent
b604e4ecc7
commit
a5d72a0dfd
@ -122,6 +122,8 @@ CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE=false
|
||||
OPENAI_ENABLED=false
|
||||
OPENAI_API_KEY_GLOBAL=
|
||||
|
||||
DIFY_ENABLED=false
|
||||
|
||||
CACHE_REDIS_ENABLED=true
|
||||
CACHE_REDIS_URI=redis://localhost:6379/6
|
||||
CACHE_REDIS_PREFIX_KEY=evolution
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-api",
|
||||
"version": "2.0.4-beta",
|
||||
"version": "2.0.4-rc",
|
||||
"description": "Rest api for communication with WhatsApp",
|
||||
"main": "./dist/src/main.js",
|
||||
"scripts": {
|
||||
|
69
src/api/integrations/dify/controllers/dify.controller.ts
Normal file
69
src/api/integrations/dify/controllers/dify.controller.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { configService, Dify } from '../../../../config/env.config';
|
||||
import { BadRequestException } from '../../../../exceptions';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { DifyDto, DifyIgnoreJidDto } from '../dto/dify.dto';
|
||||
import { DifyService } from '../services/dify.service';
|
||||
|
||||
export class DifyController {
|
||||
constructor(private readonly difyService: DifyService) {}
|
||||
|
||||
public async createDify(instance: InstanceDto, data: DifyDto) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.create(instance, data);
|
||||
}
|
||||
|
||||
public async findDify(instance: InstanceDto) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.find(instance);
|
||||
}
|
||||
|
||||
public async fetchDify(instance: InstanceDto, difyId: string) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.fetch(instance, difyId);
|
||||
}
|
||||
|
||||
public async updateDify(instance: InstanceDto, difyId: string, data: DifyDto) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.update(instance, difyId, data);
|
||||
}
|
||||
|
||||
public async deleteDify(instance: InstanceDto, difyId: string) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.delete(instance, difyId);
|
||||
}
|
||||
|
||||
public async settings(instance: InstanceDto, data: any) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.setDefaultSettings(instance, data);
|
||||
}
|
||||
|
||||
public async fetchSettings(instance: InstanceDto) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.fetchDefaultSettings(instance);
|
||||
}
|
||||
|
||||
public async changeStatus(instance: InstanceDto, data: any) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.changeStatus(instance, data);
|
||||
}
|
||||
|
||||
public async fetchSessions(instance: InstanceDto, difyId: string) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.fetchSessions(instance, difyId);
|
||||
}
|
||||
|
||||
public async ignoreJid(instance: InstanceDto, data: DifyIgnoreJidDto) {
|
||||
if (!configService.get<Dify>('DIFY').ENABLED) throw new BadRequestException('Dify is disabled');
|
||||
|
||||
return this.difyService.ignoreJid(instance, data);
|
||||
}
|
||||
}
|
46
src/api/integrations/dify/dto/dify.dto.ts
Normal file
46
src/api/integrations/dify/dto/dify.dto.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { $Enums, TriggerOperator, TriggerType } from '@prisma/client';
|
||||
|
||||
export class Session {
|
||||
remoteJid?: string;
|
||||
sessionId?: string;
|
||||
status?: string;
|
||||
createdAt?: number;
|
||||
updateAt?: number;
|
||||
}
|
||||
|
||||
export class DifyDto {
|
||||
enabled?: boolean;
|
||||
botType?: $Enums.DifyBotType;
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
expire?: number;
|
||||
keywordFinish?: string;
|
||||
delayMessage?: number;
|
||||
unknownMessage?: string;
|
||||
listeningFromMe?: boolean;
|
||||
stopBotFromMe?: boolean;
|
||||
keepOpen?: boolean;
|
||||
debounceTime?: number;
|
||||
triggerType?: TriggerType;
|
||||
triggerOperator?: TriggerOperator;
|
||||
triggerValue?: string;
|
||||
ignoreJids?: any;
|
||||
}
|
||||
|
||||
export class DifySettingDto {
|
||||
expire?: number;
|
||||
keywordFinish?: string;
|
||||
delayMessage?: number;
|
||||
unknownMessage?: string;
|
||||
listeningFromMe?: boolean;
|
||||
stopBotFromMe?: boolean;
|
||||
keepOpen?: boolean;
|
||||
debounceTime?: number;
|
||||
difyIdFallback?: string;
|
||||
ignoreJids?: any;
|
||||
}
|
||||
|
||||
export class DifyIgnoreJidDto {
|
||||
remoteJid?: string;
|
||||
action?: string;
|
||||
}
|
123
src/api/integrations/dify/routes/dify.router.ts
Normal file
123
src/api/integrations/dify/routes/dify.router.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import {
|
||||
difyIgnoreJidSchema,
|
||||
difySchema,
|
||||
difySettingSchema,
|
||||
difyStatusSchema,
|
||||
instanceSchema,
|
||||
} from '../../../../validate/validate.schema';
|
||||
import { RouterBroker } from '../../../abstract/abstract.router';
|
||||
import { InstanceDto } from '../../../dto/instance.dto';
|
||||
import { HttpStatus } from '../../../routes/index.router';
|
||||
import { difyController } from '../../../server.module';
|
||||
import { DifyDto, DifyIgnoreJidDto, DifySettingDto } from '../dto/dify.dto';
|
||||
|
||||
export class DifyRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('create'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<DifyDto>({
|
||||
request: req,
|
||||
schema: difySchema,
|
||||
ClassRef: DifyDto,
|
||||
execute: (instance, data) => difyController.createDify(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => difyController.findDify(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('fetch/:difyId'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => difyController.fetchDify(instance, req.params.difyId),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('update/:difyId'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<DifyDto>({
|
||||
request: req,
|
||||
schema: difySchema,
|
||||
ClassRef: DifyDto,
|
||||
execute: (instance, data) => difyController.updateDify(instance, req.params.difyId, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.delete(this.routerPath('delete/:difyId'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => difyController.deleteDify(instance, req.params.difyId),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('settings'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<DifySettingDto>({
|
||||
request: req,
|
||||
schema: difySettingSchema,
|
||||
ClassRef: DifySettingDto,
|
||||
execute: (instance, data) => difyController.settings(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('fetchSettings'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => difyController.fetchSettings(instance),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: difyStatusSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => difyController.changeStatus(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('fetchSessions/:difyId'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance) => difyController.fetchSessions(instance, req.params.difyId),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('ignoreJid'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<DifyIgnoreJidDto>({
|
||||
request: req,
|
||||
schema: difyIgnoreJidSchema,
|
||||
ClassRef: DifyIgnoreJidDto,
|
||||
execute: (instance, data) => difyController.ignoreJid(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
1321
src/api/integrations/dify/services/dify.service.ts
Normal file
1321
src/api/integrations/dify/services/dify.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
107
src/api/integrations/dify/validate/dify.schema.ts
Normal file
107
src/api/integrations/dify/validate/dify.schema.ts
Normal file
@ -0,0 +1,107 @@
|
||||
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 difySchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean' },
|
||||
botType: { type: 'string', enum: ['chatBot', 'textGenerator', 'agent', 'workflow'] },
|
||||
apiUrl: { type: 'string' },
|
||||
apiKey: { type: 'string' },
|
||||
triggerType: { type: 'string', enum: ['all', 'keyword', 'none'] },
|
||||
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
|
||||
triggerValue: { type: 'string' },
|
||||
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' } },
|
||||
},
|
||||
required: ['enabled', 'botType', 'triggerType'],
|
||||
...isNotEmpty('enabled', 'botType', 'triggerType'),
|
||||
};
|
||||
|
||||
export const difyStatusSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteJid: { type: 'string' },
|
||||
status: { type: 'string', enum: ['opened', 'closed', 'paused', 'delete'] },
|
||||
},
|
||||
required: ['remoteJid', 'status'],
|
||||
...isNotEmpty('remoteJid', 'status'),
|
||||
};
|
||||
|
||||
export const difySettingSchema: 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' } },
|
||||
difyIdFallback: { type: 'string' },
|
||||
},
|
||||
required: [
|
||||
'expire',
|
||||
'keywordFinish',
|
||||
'delayMessage',
|
||||
'unknownMessage',
|
||||
'listeningFromMe',
|
||||
'stopBotFromMe',
|
||||
'keepOpen',
|
||||
'debounceTime',
|
||||
'ignoreJids',
|
||||
],
|
||||
...isNotEmpty(
|
||||
'expire',
|
||||
'keywordFinish',
|
||||
'delayMessage',
|
||||
'unknownMessage',
|
||||
'listeningFromMe',
|
||||
'stopBotFromMe',
|
||||
'keepOpen',
|
||||
'debounceTime',
|
||||
'ignoreJids',
|
||||
),
|
||||
};
|
||||
|
||||
export const difyIgnoreJidSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteJid: { type: 'string' },
|
||||
action: { type: 'string', enum: ['add', 'remove'] },
|
||||
},
|
||||
required: ['remoteJid', 'action'],
|
||||
...isNotEmpty('remoteJid', 'action'),
|
||||
};
|
@ -730,7 +730,7 @@ export class OpenaiService {
|
||||
})
|
||||
.then((instance) => instance.id);
|
||||
|
||||
const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({
|
||||
const defaultSettingCheck = await this.prismaRepository.openaiSetting.findFirst({
|
||||
where: {
|
||||
instanceId,
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ import { authGuard } from '../guards/auth.guard';
|
||||
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
|
||||
import Telemetry from '../guards/telemetry.guard';
|
||||
import { ChatwootRouter } from '../integrations/chatwoot/routes/chatwoot.router';
|
||||
import { DifyRouter } from '../integrations/dify/routes/dify.router';
|
||||
import { OpenaiRouter } from '../integrations/openai/routes/openai.router';
|
||||
import { RabbitmqRouter } from '../integrations/rabbitmq/routes/rabbitmq.router';
|
||||
import { S3Router } from '../integrations/s3/routes/s3.router';
|
||||
@ -88,6 +89,7 @@ router
|
||||
.use('/label', new LabelRouter(...guards).router)
|
||||
.use('/s3', new S3Router(...guards).router)
|
||||
.use('/openai', new OpenaiRouter(...guards).router)
|
||||
.use('/dify', new DifyRouter(...guards).router)
|
||||
.get('/webhook/meta', async (req, res) => {
|
||||
if (req.query['hub.verify_token'] === configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK)
|
||||
res.send(req.query['hub.challenge']);
|
||||
|
@ -13,6 +13,8 @@ import { TemplateController } from './controllers/template.controller';
|
||||
import { WebhookController } from './controllers/webhook.controller';
|
||||
import { ChatwootController } from './integrations/chatwoot/controllers/chatwoot.controller';
|
||||
import { ChatwootService } from './integrations/chatwoot/services/chatwoot.service';
|
||||
import { DifyController } from './integrations/dify/controllers/dify.controller';
|
||||
import { DifyService } from './integrations/dify/services/dify.service';
|
||||
import { OpenaiController } from './integrations/openai/controllers/openai.controller';
|
||||
import { OpenaiService } from './integrations/openai/services/openai.service';
|
||||
import { RabbitmqController } from './integrations/rabbitmq/controllers/rabbitmq.controller';
|
||||
@ -70,6 +72,9 @@ export const typebotController = new TypebotController(typebotService);
|
||||
const openaiService = new OpenaiService(waMonitor, configService, prismaRepository);
|
||||
export const openaiController = new OpenaiController(openaiService);
|
||||
|
||||
const difyService = new DifyService(waMonitor, configService, prismaRepository);
|
||||
export const difyController = new DifyController(difyService);
|
||||
|
||||
const s3Service = new S3Service(prismaRepository);
|
||||
export const s3Controller = new S3Controller(s3Service);
|
||||
|
||||
|
@ -26,6 +26,7 @@ import { SettingsDto } from '../dto/settings.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { ChatwootDto } from '../integrations/chatwoot/dto/chatwoot.dto';
|
||||
import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service';
|
||||
import { DifyService } from '../integrations/dify/services/dify.service';
|
||||
import { OpenaiService } from '../integrations/openai/services/openai.service';
|
||||
import { RabbitmqDto } from '../integrations/rabbitmq/dto/rabbitmq.dto';
|
||||
import { getAMQP, removeQueues } from '../integrations/rabbitmq/libs/amqp.server';
|
||||
@ -71,6 +72,8 @@ export class ChannelStartupService {
|
||||
|
||||
public openaiService = new OpenaiService(waMonitor, this.configService, this.prismaRepository);
|
||||
|
||||
public difyService = new DifyService(waMonitor, this.configService, this.prismaRepository);
|
||||
|
||||
public setInstance(instance: InstanceDto) {
|
||||
this.logger.setInstance(instance.instanceName);
|
||||
|
||||
|
@ -68,6 +68,7 @@ import {
|
||||
configService,
|
||||
ConfigSessionPhone,
|
||||
Database,
|
||||
Dify,
|
||||
Log,
|
||||
Openai,
|
||||
ProviderSession,
|
||||
@ -1187,6 +1188,17 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.configService.get<Dify>('DIFY').ENABLED) {
|
||||
if (type === 'notify') {
|
||||
if (messageRaw.messageType !== 'reactionMessage')
|
||||
await this.difyService.sendDify(
|
||||
{ instanceName: this.instance.name, instanceId: this.instanceId },
|
||||
messageRaw.key.remoteJid,
|
||||
messageRaw,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const contact = await this.prismaRepository.contact.findFirst({
|
||||
where: { remoteJid: received.key.remoteJid, instanceId: this.instanceId },
|
||||
});
|
||||
|
@ -192,6 +192,7 @@ export type Chatwoot = {
|
||||
};
|
||||
};
|
||||
export type Openai = { ENABLED: boolean; API_KEY_GLOBAL?: string };
|
||||
export type Dify = { ENABLED: boolean };
|
||||
|
||||
export type S3 = {
|
||||
ACCESS_KEY: string;
|
||||
@ -226,6 +227,7 @@ export interface Env {
|
||||
TYPEBOT: Typebot;
|
||||
CHATWOOT: Chatwoot;
|
||||
OPENAI: Openai;
|
||||
DIFY: Dify;
|
||||
CACHE: CacheConf;
|
||||
S3?: S3;
|
||||
AUTHENTICATION: Auth;
|
||||
@ -437,6 +439,9 @@ export class ConfigService {
|
||||
ENABLED: process.env?.OPENAI_ENABLED === 'true',
|
||||
API_KEY_GLOBAL: process.env?.OPENAI_API_KEY_GLOBAL || null,
|
||||
},
|
||||
DIFY: {
|
||||
ENABLED: process.env?.DIFY_ENABLED === 'true',
|
||||
},
|
||||
CACHE: {
|
||||
REDIS: {
|
||||
ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Integrations Schema
|
||||
export * from '../api/integrations/chatwoot/validate/chatwoot.schema';
|
||||
export * from '../api/integrations/dify/validate/dify.schema';
|
||||
export * from '../api/integrations/openai/validate/openai.schema';
|
||||
export * from '../api/integrations/rabbitmq/validate/rabbitmq.schema';
|
||||
export * from '../api/integrations/sqs/validate/sqs.schema';
|
||||
|
Loading…
Reference in New Issue
Block a user