feat: highlight triggered or errored block (backend)

This commit is contained in:
abdou6666 2025-04-07 12:55:53 +01:00
parent b419dd5ddb
commit ae95de5d1a
6 changed files with 118 additions and 11 deletions

View File

@ -46,7 +46,7 @@ import {
} from '@/utils/test/mocks/block';
import {
contextBlankInstance,
subscriberContextBlankInstance,
subscriber,
} from '@/utils/test/mocks/conversation';
import { nlpEntitiesGreeting } from '@/utils/test/mocks/nlp';
import {
@ -429,7 +429,7 @@ describe('BlockService', () => {
...contextBlankInstance,
skip: { [blockProductListMock.id]: 0 },
},
subscriberContextBlankInstance,
subscriber,
false,
'conv_id',
);
@ -463,7 +463,7 @@ describe('BlockService', () => {
...contextBlankInstance,
skip: { [blockProductListMock.id]: 2 },
},
subscriberContextBlankInstance,
subscriber,
false,
'conv_id',
);

View File

@ -474,10 +474,11 @@ export class BlockService extends BaseService<
async processMessage(
block: Block | BlockFull,
context: Context,
subscriberContext: SubscriberContext,
recipient: Subscriber,
fallback = false,
conversationId?: string,
): Promise<StdOutgoingEnvelope> {
const subscriberContext = recipient.context as SubscriberContext;
const settings = await this.settingService.getSettings();
const blockMessage: BlockMessage =
fallback && block.options?.fallback
@ -566,6 +567,23 @@ export class BlockService extends BaseService<
const attachmentPayload = blockMessage.attachment.payload;
if (!('id' in attachmentPayload)) {
this.checkDeprecatedAttachmentUrl(block);
const flowId =
typeof block.category === 'object'
? // @ts-expect-error : block always has category
block!.category.id
: block.category;
if (flowId) {
this.logger.log('triggered: hook:highlight:error');
this.eventEmitter.emit('hook:highlight:error', {
flowId,
userId: recipient.foreign_id,
blockId: block.id,
});
} else {
this.logger.warn('Unable to trigger: hook:highlight:error');
}
throw new Error(
'Remote attachments in blocks are no longer supported!',
);
@ -614,6 +632,22 @@ export class BlockService extends BaseService<
};
return envelope;
} catch (err) {
const flowId =
typeof block.category === 'object'
? // @ts-expect-error : block always has category
block!.category.id
: block.category;
if (flowId) {
this.logger.log('triggered: hook:highlight:error');
this.eventEmitter.emit('hook:highlight:error', {
flowId,
userId: recipient.foreign_id,
blockId: block.id,
});
} else {
this.logger.warn('Unable to trigger: hook:highlight:error');
}
this.logger.error(
'Unable to retrieve content for list template process',
err,
@ -635,6 +669,22 @@ export class BlockService extends BaseService<
return envelope;
} catch (e) {
const flowId =
typeof block.category === 'object'
? // @ts-expect-error : block always has category
block!.category.id
: block.category;
if (flowId) {
this.logger.log('triggered: hook:highlight:error');
this.eventEmitter.emit('hook:highlight:error', {
flowId,
userId: recipient.foreign_id,
blockId: block.id,
});
} else {
this.logger.warn('Unable to trigger: hook:highlight:error');
}
this.logger.error('Plugin was unable to load/process ', e);
throw new Error(`Unknown plugin - ${JSON.stringify(blockMessage)}`);
}

View File

@ -147,11 +147,15 @@ export class BotService {
const envelope = await this.blockService.processMessage(
block,
context,
recipient?.context,
recipient,
fallback,
convo.id,
);
this.eventEmitter.emit('hook:highlight:block', {
flowId: block.category!.id,
blockId: block.id,
userId: recipient.foreign_id,
});
if (envelope.format !== OutgoingMessageFormat.system) {
await this.sendMessageToSubscriber(
envelope,
@ -323,6 +327,13 @@ export class BotService {
);
await this.triggerBlock(event, updatedConversation, next, fallback);
} catch (err) {
if (next && next.id !== fallbackBlock?.id) {
this.eventEmitter.emit('hook:highlight:error', {
flowId: matchedBlock!.category!.id,
userId: convo.sender.foreign_id,
blockId: next.id!,
});
}
this.logger.error('Unable to store context data!', err);
return this.eventEmitter.emit('hook:conversation:end', convo);
}
@ -522,11 +533,13 @@ export class BotService {
updatedAt: new Date(),
attachedBlock: null,
} as any as BlockFull;
const recipient = structuredClone(event.getSender());
recipient.context.vars = {};
const envelope = await this.blockService.processMessage(
globalFallbackBlock,
getDefaultConversationContext(),
{ vars: {} }, // @TODO: use subscriber ctx
recipient,
// { vars: {} }, // @TODO: use subscriber ctx
);
await this.sendMessageToSubscriber(

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 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 @@
import { Block, BlockStub } from '@/chat/schemas/block.schema';
import { ConversationFull } from '@/chat/schemas/conversation.schema';
import { Subscriber } from '@/chat/schemas/subscriber.schema';
import { Context } from '@/chat/schemas/types/context';
import { SubscriberContext } from '@/chat/schemas/types/subscriberContext';
@ -34,6 +35,10 @@ export const subscriberContextBlankInstance: SubscriberContext = {
vars: {},
};
export const subscriber = {
context: subscriberContextBlankInstance,
} as Subscriber;
export const contextEmailVarInstance: Context = {
...contextBlankInstance,
vars: {

View File

@ -6,7 +6,11 @@
* 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 { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
import {
EventEmitter2,
IHookOperationMap,
OnEvent,
} from '@nestjs/event-emitter';
import {
ConnectedSocket,
MessageBody,
@ -254,7 +258,6 @@ export class WebsocketGateway
const { sockets } = this.io.sockets;
this.logger.log(`Client id: ${client.id} connected`);
this.logger.debug(`Number of connected clients: ${sockets?.size}`);
this.eventEmitter.emit(`hook:websocket:connection`, client);
}
@ -405,4 +408,25 @@ export class WebsocketGateway
);
return response.getPromise();
}
@OnEvent('hook:highlight:block')
async handleHighlightBlock(
payload: IHookOperationMap['highlight']['operations']['block'],
) {
this.logger.log(
'broadcasting event highlight:flow through socketio ',
payload,
);
// todo: fix emit event to subscriber
this.io.emit('highlight:flow', payload);
}
@OnEvent('hook:highlight:error')
async highlightBlockErrored(
payload: IHookOperationMap['highlight']['operations']['error'],
) {
this.logger.warn('hook:highlight:error ', payload);
// todo: fix emit event to subscriber
this.io.emit('highlight:error', payload);
}
}

View File

@ -120,6 +120,21 @@ declare module '@nestjs/event-emitter' {
connection: Socket;
}
>;
highlight: TDefinition<
object,
{
block: {
userId: string;
flowId: string;
blockId: string;
};
error: {
userId: string;
flowId: string;
blockId: string;
};
}
>;
}
/* hooks */