Skip to content

Commit ae95de5

Browse files
committed
feat: highlight triggered or errored block (backend)
1 parent b419dd5 commit ae95de5

File tree

6 files changed

+118
-11
lines changed

6 files changed

+118
-11
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
} from '@/utils/test/mocks/block';
4747
import {
4848
contextBlankInstance,
49-
subscriberContextBlankInstance,
49+
subscriber,
5050
} from '@/utils/test/mocks/conversation';
5151
import { nlpEntitiesGreeting } from '@/utils/test/mocks/nlp';
5252
import {
@@ -429,7 +429,7 @@ describe('BlockService', () => {
429429
...contextBlankInstance,
430430
skip: { [blockProductListMock.id]: 0 },
431431
},
432-
subscriberContextBlankInstance,
432+
subscriber,
433433
false,
434434
'conv_id',
435435
);
@@ -463,7 +463,7 @@ describe('BlockService', () => {
463463
...contextBlankInstance,
464464
skip: { [blockProductListMock.id]: 2 },
465465
},
466-
subscriberContextBlankInstance,
466+
subscriber,
467467
false,
468468
'conv_id',
469469
);

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,11 @@ export class BlockService extends BaseService<
474474
async processMessage(
475475
block: Block | BlockFull,
476476
context: Context,
477-
subscriberContext: SubscriberContext,
477+
recipient: Subscriber,
478478
fallback = false,
479479
conversationId?: string,
480480
): Promise<StdOutgoingEnvelope> {
481+
const subscriberContext = recipient.context as SubscriberContext;
481482
const settings = await this.settingService.getSettings();
482483
const blockMessage: BlockMessage =
483484
fallback && block.options?.fallback
@@ -566,6 +567,23 @@ export class BlockService extends BaseService<
566567
const attachmentPayload = blockMessage.attachment.payload;
567568
if (!('id' in attachmentPayload)) {
568569
this.checkDeprecatedAttachmentUrl(block);
570+
571+
const flowId =
572+
typeof block.category === 'object'
573+
? // @ts-expect-error : block always has category
574+
block!.category.id
575+
: block.category;
576+
if (flowId) {
577+
this.logger.log('triggered: hook:highlight:error');
578+
this.eventEmitter.emit('hook:highlight:error', {
579+
flowId,
580+
userId: recipient.foreign_id,
581+
blockId: block.id,
582+
});
583+
} else {
584+
this.logger.warn('Unable to trigger: hook:highlight:error');
585+
}
586+
569587
throw new Error(
570588
'Remote attachments in blocks are no longer supported!',
571589
);
@@ -614,6 +632,22 @@ export class BlockService extends BaseService<
614632
};
615633
return envelope;
616634
} catch (err) {
635+
const flowId =
636+
typeof block.category === 'object'
637+
? // @ts-expect-error : block always has category
638+
block!.category.id
639+
: block.category;
640+
if (flowId) {
641+
this.logger.log('triggered: hook:highlight:error');
642+
this.eventEmitter.emit('hook:highlight:error', {
643+
flowId,
644+
userId: recipient.foreign_id,
645+
blockId: block.id,
646+
});
647+
} else {
648+
this.logger.warn('Unable to trigger: hook:highlight:error');
649+
}
650+
617651
this.logger.error(
618652
'Unable to retrieve content for list template process',
619653
err,
@@ -635,6 +669,22 @@ export class BlockService extends BaseService<
635669

636670
return envelope;
637671
} catch (e) {
672+
const flowId =
673+
typeof block.category === 'object'
674+
? // @ts-expect-error : block always has category
675+
block!.category.id
676+
: block.category;
677+
if (flowId) {
678+
this.logger.log('triggered: hook:highlight:error');
679+
this.eventEmitter.emit('hook:highlight:error', {
680+
flowId,
681+
userId: recipient.foreign_id,
682+
blockId: block.id,
683+
});
684+
} else {
685+
this.logger.warn('Unable to trigger: hook:highlight:error');
686+
}
687+
638688
this.logger.error('Plugin was unable to load/process ', e);
639689
throw new Error(`Unknown plugin - ${JSON.stringify(blockMessage)}`);
640690
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,15 @@ export class BotService {
147147
const envelope = await this.blockService.processMessage(
148148
block,
149149
context,
150-
recipient?.context,
150+
recipient,
151151
fallback,
152152
convo.id,
153153
);
154-
154+
this.eventEmitter.emit('hook:highlight:block', {
155+
flowId: block.category!.id,
156+
blockId: block.id,
157+
userId: recipient.foreign_id,
158+
});
155159
if (envelope.format !== OutgoingMessageFormat.system) {
156160
await this.sendMessageToSubscriber(
157161
envelope,
@@ -323,6 +327,13 @@ export class BotService {
323327
);
324328
await this.triggerBlock(event, updatedConversation, next, fallback);
325329
} catch (err) {
330+
if (next && next.id !== fallbackBlock?.id) {
331+
this.eventEmitter.emit('hook:highlight:error', {
332+
flowId: matchedBlock!.category!.id,
333+
userId: convo.sender.foreign_id,
334+
blockId: next.id!,
335+
});
336+
}
326337
this.logger.error('Unable to store context data!', err);
327338
return this.eventEmitter.emit('hook:conversation:end', convo);
328339
}
@@ -522,11 +533,13 @@ export class BotService {
522533
updatedAt: new Date(),
523534
attachedBlock: null,
524535
} as any as BlockFull;
525-
536+
const recipient = structuredClone(event.getSender());
537+
recipient.context.vars = {};
526538
const envelope = await this.blockService.processMessage(
527539
globalFallbackBlock,
528540
getDefaultConversationContext(),
529-
{ vars: {} }, // @TODO: use subscriber ctx
541+
recipient,
542+
// { vars: {} }, // @TODO: use subscriber ctx
530543
);
531544

532545
await this.sendMessageToSubscriber(

api/src/utils/test/mocks/conversation.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024 Hexastack. All rights reserved.
2+
* Copyright © 2025 Hexastack. All rights reserved.
33
*
44
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
55
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@@ -8,6 +8,7 @@
88

99
import { Block, BlockStub } from '@/chat/schemas/block.schema';
1010
import { ConversationFull } from '@/chat/schemas/conversation.schema';
11+
import { Subscriber } from '@/chat/schemas/subscriber.schema';
1112
import { Context } from '@/chat/schemas/types/context';
1213
import { SubscriberContext } from '@/chat/schemas/types/subscriberContext';
1314

@@ -34,6 +35,10 @@ export const subscriberContextBlankInstance: SubscriberContext = {
3435
vars: {},
3536
};
3637

38+
export const subscriber = {
39+
context: subscriberContextBlankInstance,
40+
} as Subscriber;
41+
3742
export const contextEmailVarInstance: Context = {
3843
...contextBlankInstance,
3944
vars: {

api/src/websocket/websocket.gateway.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
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 { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
9+
import {
10+
EventEmitter2,
11+
IHookOperationMap,
12+
OnEvent,
13+
} from '@nestjs/event-emitter';
1014
import {
1115
ConnectedSocket,
1216
MessageBody,
@@ -254,7 +258,6 @@ export class WebsocketGateway
254258
const { sockets } = this.io.sockets;
255259
this.logger.log(`Client id: ${client.id} connected`);
256260
this.logger.debug(`Number of connected clients: ${sockets?.size}`);
257-
258261
this.eventEmitter.emit(`hook:websocket:connection`, client);
259262
}
260263

@@ -405,4 +408,25 @@ export class WebsocketGateway
405408
);
406409
return response.getPromise();
407410
}
411+
412+
@OnEvent('hook:highlight:block')
413+
async handleHighlightBlock(
414+
payload: IHookOperationMap['highlight']['operations']['block'],
415+
) {
416+
this.logger.log(
417+
'broadcasting event highlight:flow through socketio ',
418+
payload,
419+
);
420+
// todo: fix emit event to subscriber
421+
this.io.emit('highlight:flow', payload);
422+
}
423+
424+
@OnEvent('hook:highlight:error')
425+
async highlightBlockErrored(
426+
payload: IHookOperationMap['highlight']['operations']['error'],
427+
) {
428+
this.logger.warn('hook:highlight:error ', payload);
429+
// todo: fix emit event to subscriber
430+
this.io.emit('highlight:error', payload);
431+
}
408432
}

api/types/event-emitter.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ declare module '@nestjs/event-emitter' {
120120
connection: Socket;
121121
}
122122
>;
123+
highlight: TDefinition<
124+
object,
125+
{
126+
block: {
127+
userId: string;
128+
flowId: string;
129+
blockId: string;
130+
};
131+
error: {
132+
userId: string;
133+
flowId: string;
134+
blockId: string;
135+
};
136+
}
137+
>;
123138
}
124139

125140
/* hooks */

0 commit comments

Comments
 (0)