feat: add setting to enable/disable highlight block & fix logic to join socketio room

This commit is contained in:
abdou6666
2025-05-05 17:20:59 +01:00
parent b2c4335526
commit 6ee59d450a
11 changed files with 160 additions and 19 deletions

View File

@@ -49,12 +49,15 @@ import { BlockCreateDto, BlockUpdateDto } from '../dto/block.dto';
import { BlockRepository } from '../repositories/block.repository';
import { CategoryRepository } from '../repositories/category.repository';
import { LabelRepository } from '../repositories/label.repository';
import { SubscriberRepository } from '../repositories/subscriber.repository';
import { Block, BlockModel } from '../schemas/block.schema';
import { LabelModel } from '../schemas/label.schema';
import { SubscriberModel } from '../schemas/subscriber.schema';
import { PayloadType } from '../schemas/types/button';
import { BlockService } from '../services/block.service';
import { CategoryService } from '../services/category.service';
import { LabelService } from '../services/label.service';
import { SubscriberService } from '../services/subscriber.service';
import { Category, CategoryModel } from './../schemas/category.schema';
import { BlockController } from './block.controller';
@@ -93,6 +96,7 @@ describe('BlockController', () => {
RoleModel,
PermissionModel,
LanguageModel,
SubscriberModel,
]),
],
providers: [
@@ -116,6 +120,8 @@ describe('BlockController', () => {
PermissionService,
LanguageService,
PluginService,
SubscriberService,
SubscriberRepository,
{
provide: I18nService,
useValue: {

View File

@@ -6,11 +6,19 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { CACHE_MANAGER, CacheModule } from '@nestjs/cache-manager';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { MongooseModule } from '@nestjs/mongoose';
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service';
import { LoggerModule } from '@/logger/logger.module';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { SettingModule } from '@/setting/setting.module';
import { InvitationRepository } from '@/user/repositories/invitation.repository';
import { RoleRepository } from '@/user/repositories/role.repository';
import { UserRepository } from '@/user/repositories/user.repository';
@@ -57,6 +65,29 @@ describe('SubscriberController', () => {
const { getMocks } = await buildTestingMocks({
controllers: [SubscriberController],
imports: [
CacheModule.register({
isGlobal: true,
ttl: 60 * 1000,
max: 100,
}),
EventEmitterModule.forRoot({
// set this to `true` to use wildcards
wildcard: true,
// the delimiter used to segment namespaces
delimiter: ':',
// set this to `true` if you want to emit the newListener event
newListener: false,
// set this to `true` if you want to emit the removeListener event
removeListener: false,
// the maximum amount of listeners that can be assigned to an event
maxListeners: 10,
// show event name in memory leak message when more than maximum amount of listeners is assigned
verboseMemoryLeak: false,
// disable throwing uncaughtException if an error event is emitted and it has no listeners
ignoreErrors: false,
}),
LoggerModule,
SettingModule,
rootMongooseTestModule(installSubscriberFixtures),
MongooseModule.forFeature([
SubscriberModel,
@@ -66,6 +97,7 @@ describe('SubscriberController', () => {
InvitationModel,
PermissionModel,
AttachmentModel,
SettingModel,
]),
],
providers: [
@@ -82,6 +114,17 @@ describe('SubscriberController', () => {
InvitationRepository,
AttachmentService,
AttachmentRepository,
SettingService,
SettingSeeder,
SettingRepository,
{
provide: CACHE_MANAGER,
useValue: {
del: jest.fn(),
get: jest.fn(),
set: jest.fn(),
},
},
],
});
[subscriberService, labelService, userService, subscriberController] =

View File

@@ -56,15 +56,18 @@ import {
import { buildTestingMocks } from '@/utils/test/utils';
import { BlockRepository } from '../repositories/block.repository';
import { SubscriberRepository } from '../repositories/subscriber.repository';
import { Block, BlockModel } from '../schemas/block.schema';
import { Category, CategoryModel } from '../schemas/category.schema';
import { LabelModel } from '../schemas/label.schema';
import { SubscriberModel } from '../schemas/subscriber.schema';
import { FileType } from '../schemas/types/attachment';
import { StdOutgoingListMessage } from '../schemas/types/message';
import { CategoryRepository } from './../repositories/category.repository';
import { BlockService } from './block.service';
import { CategoryService } from './category.service';
import { SubscriberService } from './subscriber.service';
describe('BlockService', () => {
let blockRepository: BlockRepository;
@@ -91,6 +94,7 @@ describe('BlockService', () => {
AttachmentModel,
LabelModel,
LanguageModel,
SubscriberModel,
]),
],
providers: [
@@ -106,6 +110,8 @@ describe('BlockService', () => {
ContentService,
AttachmentService,
LanguageService,
SubscriberService,
SubscriberRepository,
{
provide: PluginService,
useValue: {},

View File

@@ -52,6 +52,8 @@ import { NlpPattern, PayloadPattern } from '../schemas/types/pattern';
import { Payload, StdQuickReply } from '../schemas/types/quick-reply';
import { SubscriberContext } from '../schemas/types/subscriberContext';
import { SubscriberService } from './subscriber.service';
@Injectable()
export class BlockService extends BaseService<
Block,
@@ -68,6 +70,7 @@ export class BlockService extends BaseService<
private readonly pluginService: PluginService,
protected readonly i18n: I18nService,
protected readonly languageService: LanguageService,
private subscriberService: SubscriberService,
@Optional() gateway?: WebsocketGateway,
) {
super(repository);
@@ -78,19 +81,33 @@ export class BlockService extends BaseService<
@SocketGet('/block/subscribe/')
@SocketPost('/block/subscribe/')
subscribe(@SocketReq() req: SocketRequest, @SocketRes() res: SocketResponse) {
async subscribe(
@SocketReq() req: SocketRequest,
@SocketRes() res: SocketResponse,
) {
try {
if (req.session.web?.profile?.id) {
const room = `blocks:${req.session.web.profile.id}`;
this.gateway.io.socketsJoin(room);
this.logger.log('Subscribed to socket room', room);
return res.status(200).json({
success: true,
});
} else {
this.logger.error('Unable to subscribe to highlight blocks room');
throw new Error('Unable to join highlight blocks room');
const subscriberForeignId = req.session.passport?.user?.id;
if (!subscriberForeignId) {
this.logger.warn('Missing subscriber foreign ID in session');
throw new Error('Invalid session or user not authenticated');
}
const subscriber = await this.subscriberService.findOne({
foreign_id: subscriberForeignId,
});
if (!subscriber) {
this.logger.warn(
`Subscriber not found for foreign ID ${subscriberForeignId}`,
);
throw new Error('Subscriber not found');
}
const room = `blocks:${subscriber.id}`;
this.gateway.io.socketsJoin(room);
this.logger.log('Subscribed to socket room', room);
return res.status(200).json({
success: true,
});
} catch (e) {
this.logger.error('Websocket subscription', e);
throw new InternalServerErrorException(e);

View File

@@ -85,6 +85,13 @@ export const DEFAULT_SETTINGS = [
weight: 6,
translatable: true,
},
{
group: 'chatbot_settings',
label: 'enable_debug',
value: false,
type: SettingType.checkbox,
weight: 7,
},
{
group: 'contact',
label: 'contact_email_recipient',

View File

@@ -164,4 +164,9 @@ export class SettingService extends BaseService<Setting> {
const settings = await this.findAll();
return this.buildTree(settings);
}
public async isHighlightEnabled(): Promise<boolean> {
const settings = await this.getSettings();
return settings.chatbot_settings.enable_debug ?? false;
}
}

View File

@@ -24,6 +24,10 @@ import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { getRandom } from '@/utils/helpers/safeRandom';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures } from '@/utils/test/fixtures/user';
@@ -76,6 +80,7 @@ describe('AuthController', () => {
InvitationModel,
AttachmentModel,
LanguageModel,
SettingModel,
]),
],
providers: [
@@ -94,6 +99,9 @@ describe('AuthController', () => {
LanguageRepository,
LanguageService,
JwtService,
SettingService,
SettingSeeder,
SettingRepository,
{
provide: MailerService,
useValue: {

View File

@@ -6,10 +6,18 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { CacheModule } from '@nestjs/cache-manager';
import { INestApplication } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { MongooseModule } from '@nestjs/mongoose';
import { Socket, io } from 'socket.io-client';
import { LoggerModule } from '@/logger/logger.module';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { SettingModule } from '@/setting/setting.module';
import {
closeInMongodConnection,
rootMongooseTestModule,
@@ -29,15 +37,41 @@ describe('WebsocketGateway', () => {
const { module } = await buildTestingMocks({
providers: [
WebsocketGateway,
EventEmitter2,
SettingService,
SettingSeeder,
SettingRepository,
SocketEventDispatcherService,
],
imports: [
CacheModule.register({
isGlobal: true,
ttl: 60 * 1000,
max: 100,
}),
EventEmitterModule.forRoot({
// set this to `true` to use wildcards
wildcard: true,
// the delimiter used to segment namespaces
delimiter: ':',
// set this to `true` if you want to emit the newListener event
newListener: false,
// set this to `true` if you want to emit the removeListener event
removeListener: false,
// the maximum amount of listeners that can be assigned to an event
maxListeners: 10,
// show event name in memory leak message when more than maximum amount of listeners is assigned
verboseMemoryLeak: false,
// disable throwing uncaughtException if an error event is emitted and it has no listeners
ignoreErrors: false,
}),
LoggerModule,
SettingModule,
rootMongooseTestModule(({ uri, dbName }) => {
process.env.MONGO_URI = uri;
process.env.MONGO_DB = dbName;
return Promise.resolve();
}),
MongooseModule.forFeature([SettingModel]),
],
});
app = module.createNestApplication();

View File

@@ -39,6 +39,7 @@ import { config } from '@/config';
import { LoggerService } from '@/logger/logger.service';
import { getSessionStore } from '@/utils/constants/session-store';
import { SettingService } from './../setting/services/setting.service';
import { IOIncomingMessage, IOMessagePipe } from './pipes/io-message.pipe';
import { SocketEventDispatcherService } from './services/socket-event-dispatcher.service';
import { Room } from './types';
@@ -54,6 +55,7 @@ export class WebsocketGateway
private readonly logger: LoggerService,
private readonly eventEmitter: EventEmitter2,
private readonly socketEventDispatcherService: SocketEventDispatcherService,
private settingService: SettingService,
) {}
@WebSocketServer() io: Server;
@@ -413,15 +415,24 @@ export class WebsocketGateway
async handleHighlightBlock(
payload: IHookOperationMap['highlight']['operations']['block'],
) {
this.logger.log('highlighting block', payload);
const isHighlightEnabled = await this.settingService.isHighlightEnabled();
if (!isHighlightEnabled) {
return;
}
this.io.to(`blocks:${payload.userId}`).emit('highlight:block', payload);
this.logger.log('highlighting block', payload);
}
@OnEvent('hook:highlight:error')
async highlightBlockErrored(
payload: IHookOperationMap['highlight']['operations']['error'],
) {
this.logger.warn('hook:highlight:error', payload);
const isHighlightEnabled = await this.settingService.isHighlightEnabled();
if (!isHighlightEnabled) {
return;
}
this.io.to(`blocks:${payload.userId}`).emit('highlight:error', payload);
this.logger.warn('hook:highlight:error', payload);
}
}

View File

@@ -8,13 +8,15 @@
"fallback_block": "Fallback Block",
"default_nlu_helper": "Default NLU Helper",
"default_llm_helper": "Default LLM Helper",
"default_storage_helper": "Default Storage Helper"
"default_storage_helper": "Default Storage Helper",
"enable_debug": "Enable flow debugger?"
},
"help": {
"global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.",
"fallback_message": "If no fallback block is selected, then one of these messages will be sent.",
"default_nlu_helper": "The NLU helper is responsible for processing and understanding user inputs, including tasks like intent prediction, language detection, and entity recognition.",
"default_llm_helper": "The LLM helper leverages advanced generative AI to perform tasks such as text generation, chat completion, and complex query responses.",
"default_storage_helper": "The storage helper defines where to store attachment files. By default, the default local storage helper stores them locally, but you can choose to use Minio or any other storage solution."
"default_storage_helper": "The storage helper defines where to store attachment files. By default, the default local storage helper stores them locally, but you can choose to use Minio or any other storage solution.",
"enable_debug": "When enabled, this highlights which blocks were executed or failed during a flow run, helping you visually debug the flow logic in real time."
}
}

View File

@@ -8,13 +8,15 @@
"fallback_block": "Bloc de secours",
"default_nlu_helper": "Utilitaire NLU par défaut",
"default_llm_helper": "Utilitaire LLM par défaut",
"default_storage_helper": "Utilitaire de stockage par défaut"
"default_storage_helper": "Utilitaire de stockage par défaut",
"enable_debug": "Activer la débogueur de flux?"
},
"help": {
"global_fallback": "La réponse de secours globale vous permet d'envoyer des messages personnalisés lorsque l'entrée de l'utilisateur ne correspond à aucun des messages des blocs.",
"fallback_message": "Si aucun bloc de secours n'est sélectionné, l'un de ces messages sera envoyé.",
"default_nlu_helper": "Utilitaire du traitement et de la compréhension des entrées des utilisateurs, incluant des tâches telles que la prédiction d'intention, la détection de langue et la reconnaissance d'entités.",
"default_llm_helper": "Utilitaire responsable de l'intelligence artificielle générative avancée pour effectuer des tâches telles que la génération de texte, la complétion de chat et les réponses à des requêtes complexes.",
"default_storage_helper": "Utilitaire de stockage définit l'emplacement où stocker les fichiers joints. Par défaut, le stockage local les conserve localement, mais vous pouvez choisir d'utiliser Minio ou toute autre solution de stockage."
"default_storage_helper": "Utilitaire de stockage définit l'emplacement où stocker les fichiers joints. Par défaut, le stockage local les conserve localement, mais vous pouvez choisir d'utiliser Minio ou toute autre solution de stockage.",
"enable_debug": "Lorsquil est activé, cela met en surbrillance les blocs qui ont été exécutés ou ont échoué pendant lexécution dun flux, ce qui vous aide à déboguer visuellement la logique du flux en temps réel."
}
}