mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: add setting to enable/disable highlight block & fix logic to join socketio room
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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] =
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "Lorsqu’il est activé, cela met en surbrillance les blocs qui ont été exécutés ou ont échoué pendant l’exécution d’un flux, ce qui vous aide à déboguer visuellement la logique du flux en temps réel."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user