Skip to content

Commit 6ee59d4

Browse files
committed
feat: add setting to enable/disable highlight block & fix logic to join socketio room
1 parent b2c4335 commit 6ee59d4

File tree

11 files changed

+160
-19
lines changed

11 files changed

+160
-19
lines changed

api/src/chat/controllers/block.controller.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,15 @@ import { BlockCreateDto, BlockUpdateDto } from '../dto/block.dto';
4949
import { BlockRepository } from '../repositories/block.repository';
5050
import { CategoryRepository } from '../repositories/category.repository';
5151
import { LabelRepository } from '../repositories/label.repository';
52+
import { SubscriberRepository } from '../repositories/subscriber.repository';
5253
import { Block, BlockModel } from '../schemas/block.schema';
5354
import { LabelModel } from '../schemas/label.schema';
55+
import { SubscriberModel } from '../schemas/subscriber.schema';
5456
import { PayloadType } from '../schemas/types/button';
5557
import { BlockService } from '../services/block.service';
5658
import { CategoryService } from '../services/category.service';
5759
import { LabelService } from '../services/label.service';
60+
import { SubscriberService } from '../services/subscriber.service';
5861

5962
import { Category, CategoryModel } from './../schemas/category.schema';
6063
import { BlockController } from './block.controller';
@@ -93,6 +96,7 @@ describe('BlockController', () => {
9396
RoleModel,
9497
PermissionModel,
9598
LanguageModel,
99+
SubscriberModel,
96100
]),
97101
],
98102
providers: [
@@ -116,6 +120,8 @@ describe('BlockController', () => {
116120
PermissionService,
117121
LanguageService,
118122
PluginService,
123+
SubscriberService,
124+
SubscriberRepository,
119125
{
120126
provide: I18nService,
121127
useValue: {

api/src/chat/controllers/subscriber.controller.spec.ts

+43
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66
* 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).
77
*/
88

9+
import { CACHE_MANAGER, CacheModule } from '@nestjs/cache-manager';
10+
import { EventEmitterModule } from '@nestjs/event-emitter';
911
import { MongooseModule } from '@nestjs/mongoose';
1012

1113
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
1214
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
1315
import { AttachmentService } from '@/attachment/services/attachment.service';
16+
import { LoggerModule } from '@/logger/logger.module';
17+
import { SettingRepository } from '@/setting/repositories/setting.repository';
18+
import { SettingModel } from '@/setting/schemas/setting.schema';
19+
import { SettingSeeder } from '@/setting/seeds/setting.seed';
20+
import { SettingService } from '@/setting/services/setting.service';
21+
import { SettingModule } from '@/setting/setting.module';
1422
import { InvitationRepository } from '@/user/repositories/invitation.repository';
1523
import { RoleRepository } from '@/user/repositories/role.repository';
1624
import { UserRepository } from '@/user/repositories/user.repository';
@@ -57,6 +65,29 @@ describe('SubscriberController', () => {
5765
const { getMocks } = await buildTestingMocks({
5866
controllers: [SubscriberController],
5967
imports: [
68+
CacheModule.register({
69+
isGlobal: true,
70+
ttl: 60 * 1000,
71+
max: 100,
72+
}),
73+
EventEmitterModule.forRoot({
74+
// set this to `true` to use wildcards
75+
wildcard: true,
76+
// the delimiter used to segment namespaces
77+
delimiter: ':',
78+
// set this to `true` if you want to emit the newListener event
79+
newListener: false,
80+
// set this to `true` if you want to emit the removeListener event
81+
removeListener: false,
82+
// the maximum amount of listeners that can be assigned to an event
83+
maxListeners: 10,
84+
// show event name in memory leak message when more than maximum amount of listeners is assigned
85+
verboseMemoryLeak: false,
86+
// disable throwing uncaughtException if an error event is emitted and it has no listeners
87+
ignoreErrors: false,
88+
}),
89+
LoggerModule,
90+
SettingModule,
6091
rootMongooseTestModule(installSubscriberFixtures),
6192
MongooseModule.forFeature([
6293
SubscriberModel,
@@ -66,6 +97,7 @@ describe('SubscriberController', () => {
6697
InvitationModel,
6798
PermissionModel,
6899
AttachmentModel,
100+
SettingModel,
69101
]),
70102
],
71103
providers: [
@@ -82,6 +114,17 @@ describe('SubscriberController', () => {
82114
InvitationRepository,
83115
AttachmentService,
84116
AttachmentRepository,
117+
SettingService,
118+
SettingSeeder,
119+
SettingRepository,
120+
{
121+
provide: CACHE_MANAGER,
122+
useValue: {
123+
del: jest.fn(),
124+
get: jest.fn(),
125+
set: jest.fn(),
126+
},
127+
},
85128
],
86129
});
87130
[subscriberService, labelService, userService, subscriberController] =

api/src/chat/services/block.service.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,18 @@ import {
5656
import { buildTestingMocks } from '@/utils/test/utils';
5757

5858
import { BlockRepository } from '../repositories/block.repository';
59+
import { SubscriberRepository } from '../repositories/subscriber.repository';
5960
import { Block, BlockModel } from '../schemas/block.schema';
6061
import { Category, CategoryModel } from '../schemas/category.schema';
6162
import { LabelModel } from '../schemas/label.schema';
63+
import { SubscriberModel } from '../schemas/subscriber.schema';
6264
import { FileType } from '../schemas/types/attachment';
6365
import { StdOutgoingListMessage } from '../schemas/types/message';
6466

6567
import { CategoryRepository } from './../repositories/category.repository';
6668
import { BlockService } from './block.service';
6769
import { CategoryService } from './category.service';
70+
import { SubscriberService } from './subscriber.service';
6871

6972
describe('BlockService', () => {
7073
let blockRepository: BlockRepository;
@@ -91,6 +94,7 @@ describe('BlockService', () => {
9194
AttachmentModel,
9295
LabelModel,
9396
LanguageModel,
97+
SubscriberModel,
9498
]),
9599
],
96100
providers: [
@@ -106,6 +110,8 @@ describe('BlockService', () => {
106110
ContentService,
107111
AttachmentService,
108112
LanguageService,
113+
SubscriberService,
114+
SubscriberRepository,
109115
{
110116
provide: PluginService,
111117
useValue: {},

api/src/chat/services/block.service.ts

+28-11
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import { NlpPattern, PayloadPattern } from '../schemas/types/pattern';
5252
import { Payload, StdQuickReply } from '../schemas/types/quick-reply';
5353
import { SubscriberContext } from '../schemas/types/subscriberContext';
5454

55+
import { SubscriberService } from './subscriber.service';
56+
5557
@Injectable()
5658
export class BlockService extends BaseService<
5759
Block,
@@ -68,6 +70,7 @@ export class BlockService extends BaseService<
6870
private readonly pluginService: PluginService,
6971
protected readonly i18n: I18nService,
7072
protected readonly languageService: LanguageService,
73+
private subscriberService: SubscriberService,
7174
@Optional() gateway?: WebsocketGateway,
7275
) {
7376
super(repository);
@@ -78,19 +81,33 @@ export class BlockService extends BaseService<
7881

7982
@SocketGet('/block/subscribe/')
8083
@SocketPost('/block/subscribe/')
81-
subscribe(@SocketReq() req: SocketRequest, @SocketRes() res: SocketResponse) {
84+
async subscribe(
85+
@SocketReq() req: SocketRequest,
86+
@SocketRes() res: SocketResponse,
87+
) {
8288
try {
83-
if (req.session.web?.profile?.id) {
84-
const room = `blocks:${req.session.web.profile.id}`;
85-
this.gateway.io.socketsJoin(room);
86-
this.logger.log('Subscribed to socket room', room);
87-
return res.status(200).json({
88-
success: true,
89-
});
90-
} else {
91-
this.logger.error('Unable to subscribe to highlight blocks room');
92-
throw new Error('Unable to join highlight blocks room');
89+
const subscriberForeignId = req.session.passport?.user?.id;
90+
if (!subscriberForeignId) {
91+
this.logger.warn('Missing subscriber foreign ID in session');
92+
throw new Error('Invalid session or user not authenticated');
9393
}
94+
95+
const subscriber = await this.subscriberService.findOne({
96+
foreign_id: subscriberForeignId,
97+
});
98+
99+
if (!subscriber) {
100+
this.logger.warn(
101+
`Subscriber not found for foreign ID ${subscriberForeignId}`,
102+
);
103+
throw new Error('Subscriber not found');
104+
}
105+
const room = `blocks:${subscriber.id}`;
106+
this.gateway.io.socketsJoin(room);
107+
this.logger.log('Subscribed to socket room', room);
108+
return res.status(200).json({
109+
success: true,
110+
});
94111
} catch (e) {
95112
this.logger.error('Websocket subscription', e);
96113
throw new InternalServerErrorException(e);

api/src/setting/seeds/setting.seed-model.ts

+7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export const DEFAULT_SETTINGS = [
8585
weight: 6,
8686
translatable: true,
8787
},
88+
{
89+
group: 'chatbot_settings',
90+
label: 'enable_debug',
91+
value: false,
92+
type: SettingType.checkbox,
93+
weight: 7,
94+
},
8895
{
8996
group: 'contact',
9097
label: 'contact_email_recipient',

api/src/setting/services/setting.service.ts

+5
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,9 @@ export class SettingService extends BaseService<Setting> {
164164
const settings = await this.findAll();
165165
return this.buildTree(settings);
166166
}
167+
168+
public async isHighlightEnabled(): Promise<boolean> {
169+
const settings = await this.getSettings();
170+
return settings.chatbot_settings.enable_debug ?? false;
171+
}
167172
}

api/src/user/controllers/auth.controller.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import { LanguageRepository } from '@/i18n/repositories/language.repository';
2424
import { LanguageModel } from '@/i18n/schemas/language.schema';
2525
import { I18nService } from '@/i18n/services/i18n.service';
2626
import { LanguageService } from '@/i18n/services/language.service';
27+
import { SettingRepository } from '@/setting/repositories/setting.repository';
28+
import { SettingModel } from '@/setting/schemas/setting.schema';
29+
import { SettingSeeder } from '@/setting/seeds/setting.seed';
30+
import { SettingService } from '@/setting/services/setting.service';
2731
import { getRandom } from '@/utils/helpers/safeRandom';
2832
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
2933
import { installUserFixtures } from '@/utils/test/fixtures/user';
@@ -76,6 +80,7 @@ describe('AuthController', () => {
7680
InvitationModel,
7781
AttachmentModel,
7882
LanguageModel,
83+
SettingModel,
7984
]),
8085
],
8186
providers: [
@@ -94,6 +99,9 @@ describe('AuthController', () => {
9499
LanguageRepository,
95100
LanguageService,
96101
JwtService,
102+
SettingService,
103+
SettingSeeder,
104+
SettingRepository,
97105
{
98106
provide: MailerService,
99107
useValue: {

api/src/websocket/websocket.gateway.spec.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@
66
* 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).
77
*/
88

9+
import { CacheModule } from '@nestjs/cache-manager';
910
import { INestApplication } from '@nestjs/common';
10-
import { EventEmitter2 } from '@nestjs/event-emitter';
11+
import { EventEmitterModule } from '@nestjs/event-emitter';
12+
import { MongooseModule } from '@nestjs/mongoose';
1113
import { Socket, io } from 'socket.io-client';
1214

15+
import { LoggerModule } from '@/logger/logger.module';
16+
import { SettingRepository } from '@/setting/repositories/setting.repository';
17+
import { SettingModel } from '@/setting/schemas/setting.schema';
18+
import { SettingSeeder } from '@/setting/seeds/setting.seed';
19+
import { SettingService } from '@/setting/services/setting.service';
20+
import { SettingModule } from '@/setting/setting.module';
1321
import {
1422
closeInMongodConnection,
1523
rootMongooseTestModule,
@@ -29,15 +37,41 @@ describe('WebsocketGateway', () => {
2937
const { module } = await buildTestingMocks({
3038
providers: [
3139
WebsocketGateway,
32-
EventEmitter2,
40+
SettingService,
41+
SettingSeeder,
42+
SettingRepository,
3343
SocketEventDispatcherService,
3444
],
3545
imports: [
46+
CacheModule.register({
47+
isGlobal: true,
48+
ttl: 60 * 1000,
49+
max: 100,
50+
}),
51+
EventEmitterModule.forRoot({
52+
// set this to `true` to use wildcards
53+
wildcard: true,
54+
// the delimiter used to segment namespaces
55+
delimiter: ':',
56+
// set this to `true` if you want to emit the newListener event
57+
newListener: false,
58+
// set this to `true` if you want to emit the removeListener event
59+
removeListener: false,
60+
// the maximum amount of listeners that can be assigned to an event
61+
maxListeners: 10,
62+
// show event name in memory leak message when more than maximum amount of listeners is assigned
63+
verboseMemoryLeak: false,
64+
// disable throwing uncaughtException if an error event is emitted and it has no listeners
65+
ignoreErrors: false,
66+
}),
67+
LoggerModule,
68+
SettingModule,
3669
rootMongooseTestModule(({ uri, dbName }) => {
3770
process.env.MONGO_URI = uri;
3871
process.env.MONGO_DB = dbName;
3972
return Promise.resolve();
4073
}),
74+
MongooseModule.forFeature([SettingModel]),
4175
],
4276
});
4377
app = module.createNestApplication();

api/src/websocket/websocket.gateway.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { config } from '@/config';
3939
import { LoggerService } from '@/logger/logger.service';
4040
import { getSessionStore } from '@/utils/constants/session-store';
4141

42+
import { SettingService } from './../setting/services/setting.service';
4243
import { IOIncomingMessage, IOMessagePipe } from './pipes/io-message.pipe';
4344
import { SocketEventDispatcherService } from './services/socket-event-dispatcher.service';
4445
import { Room } from './types';
@@ -54,6 +55,7 @@ export class WebsocketGateway
5455
private readonly logger: LoggerService,
5556
private readonly eventEmitter: EventEmitter2,
5657
private readonly socketEventDispatcherService: SocketEventDispatcherService,
58+
private settingService: SettingService,
5759
) {}
5860

5961
@WebSocketServer() io: Server;
@@ -413,15 +415,24 @@ export class WebsocketGateway
413415
async handleHighlightBlock(
414416
payload: IHookOperationMap['highlight']['operations']['block'],
415417
) {
416-
this.logger.log('highlighting block', payload);
418+
const isHighlightEnabled = await this.settingService.isHighlightEnabled();
419+
if (!isHighlightEnabled) {
420+
return;
421+
}
417422
this.io.to(`blocks:${payload.userId}`).emit('highlight:block', payload);
423+
this.logger.log('highlighting block', payload);
418424
}
419425

420426
@OnEvent('hook:highlight:error')
421427
async highlightBlockErrored(
422428
payload: IHookOperationMap['highlight']['operations']['error'],
423429
) {
424-
this.logger.warn('hook:highlight:error', payload);
430+
const isHighlightEnabled = await this.settingService.isHighlightEnabled();
431+
if (!isHighlightEnabled) {
432+
return;
433+
}
434+
425435
this.io.to(`blocks:${payload.userId}`).emit('highlight:error', payload);
436+
this.logger.warn('hook:highlight:error', payload);
426437
}
427438
}

frontend/public/locales/en/chatbot_settings.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
"fallback_block": "Fallback Block",
99
"default_nlu_helper": "Default NLU Helper",
1010
"default_llm_helper": "Default LLM Helper",
11-
"default_storage_helper": "Default Storage Helper"
11+
"default_storage_helper": "Default Storage Helper",
12+
"enable_debug": "Enable flow debugger?"
1213
},
1314
"help": {
1415
"global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.",
1516
"fallback_message": "If no fallback block is selected, then one of these messages will be sent.",
1617
"default_nlu_helper": "The NLU helper is responsible for processing and understanding user inputs, including tasks like intent prediction, language detection, and entity recognition.",
1718
"default_llm_helper": "The LLM helper leverages advanced generative AI to perform tasks such as text generation, chat completion, and complex query responses.",
18-
"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."
19+
"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.",
20+
"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."
1921
}
2022
}

0 commit comments

Comments
 (0)