diff --git a/api/src/chat/services/block.service.spec.ts b/api/src/chat/services/block.service.spec.ts index 393d9160..ea4265fb 100644 --- a/api/src/chat/services/block.service.spec.ts +++ b/api/src/chat/services/block.service.spec.ts @@ -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', ); diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index 5f0dbbb8..06dfb3c9 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -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 { + 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)}`); } diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index 13e6cd57..392a074e 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -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( diff --git a/api/src/utils/test/mocks/conversation.ts b/api/src/utils/test/mocks/conversation.ts index 115b9d2f..dd89182d 100644 --- a/api/src/utils/test/mocks/conversation.ts +++ b/api/src/utils/test/mocks/conversation.ts @@ -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: { diff --git a/api/src/websocket/websocket.gateway.ts b/api/src/websocket/websocket.gateway.ts index f6e1b0f6..9141e441 100644 --- a/api/src/websocket/websocket.gateway.ts +++ b/api/src/websocket/websocket.gateway.ts @@ -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); + } } diff --git a/api/types/event-emitter.d.ts b/api/types/event-emitter.d.ts index 6d9ce2d3..6723d8f7 100644 --- a/api/types/event-emitter.d.ts +++ b/api/types/event-emitter.d.ts @@ -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 */