From d88959cea2bbf597486997ea72539700a9fed6f9 Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Wed, 11 Jun 2025 14:29:34 +0100 Subject: [PATCH 1/3] refactor: handle ongoing conversation message --- api/src/chat/services/bot.service.spec.ts | 122 ++++++++++++++++++++-- api/src/chat/services/bot.service.ts | 77 +++++++++----- 2 files changed, 162 insertions(+), 37 deletions(-) diff --git a/api/src/chat/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index d8b61596..9fb7fa87 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -88,6 +88,7 @@ import { SubscriberService } from './subscriber.service'; describe('BotService', () => { let blockService: BlockService; let subscriberService: SubscriberService; + let conversationService: ConversationService; let botService: BotService; let handler: WebChannelHandler; let eventEmitter: EventEmitter2; @@ -192,14 +193,21 @@ describe('BotService', () => { }, ], }); - [subscriberService, botService, blockService, eventEmitter, handler] = - await getMocks([ - SubscriberService, - BotService, - BlockService, - EventEmitter2, - WebChannelHandler, - ]); + [ + subscriberService, + conversationService, + botService, + blockService, + eventEmitter, + handler, + ] = await getMocks([ + SubscriberService, + ConversationService, + BotService, + BlockService, + EventEmitter2, + WebChannelHandler, + ]); }); afterEach(jest.clearAllMocks); @@ -351,4 +359,102 @@ describe('BotService', () => { expect(captured).toBe(false); expect(triggeredEvents).toEqual([]); }); + + describe('proceedToNextBlock', () => { + it('should emit stats and call triggerBlock, returning true on success and reset attempt if not fallback', async () => { + const convo = { + id: 'convo1', + context: { attempt: 2 }, + next: [], + sender: 'user1', + active: true, + } as unknown as ConversationFull; + const next = { id: 'block1', name: 'Block 1' } as BlockFull; + const event = {} as any; + const fallback = false; + + jest + .spyOn(conversationService, 'storeContextData') + .mockImplementation((convo, _next, _event, _captureVars) => { + return Promise.resolve({ + ...convo, + } as Conversation); + }); + + jest.spyOn(botService, 'triggerBlock').mockResolvedValue(undefined); + const emitSpy = jest.spyOn(eventEmitter, 'emit'); + const result = await botService.proceedToNextBlock( + convo, + next, + event, + fallback, + ); + + expect(emitSpy).toHaveBeenCalledWith( + 'hook:stats:entry', + 'popular', + next.name, + ); + + expect(botService.triggerBlock).toHaveBeenCalledWith( + event, + expect.objectContaining({ id: 'convo1' }), + next, + fallback, + ); + expect(result).toBe(true); + expect(convo.context.attempt).toBe(0); + }); + + it('should increment attempt if fallback is true', async () => { + const convo = { + id: 'convo2', + context: { attempt: 1 }, + next: [], + sender: 'user2', + active: true, + } as any; + const next = { id: 'block2', name: 'Block 2' } as any; + const event = {} as any; + const fallback = true; + + const result = await botService.proceedToNextBlock( + convo, + next, + event, + fallback, + ); + + expect(convo.context.attempt).toBe(2); + expect(result).toBe(true); + }); + + it('should handle errors and emit conversation:end, returning false', async () => { + const convo = { + id: 'convo3', + context: { attempt: 1 }, + next: [], + sender: 'user3', + active: true, + } as any; + const next = { id: 'block3', name: 'Block 3' } as any; + const event = {} as any; + const fallback = false; + + jest + .spyOn(conversationService, 'storeContextData') + .mockRejectedValue(new Error('fail')); + + const emitSpy = jest.spyOn(eventEmitter, 'emit'); + const result = await botService.proceedToNextBlock( + convo, + next, + event, + fallback, + ); + + expect(emitSpy).toHaveBeenCalledWith('hook:conversation:end', convo); + expect(result).toBe(false); + }); + }); }); diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index ae3dac76..4007d70e 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -11,6 +11,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { BotStatsType } from '@/analytics/schemas/bot-stats.schema'; import EventWrapper from '@/channel/lib/EventWrapper'; +import { HelperService } from '@/helper/helper.service'; import { LoggerService } from '@/logger/logger.service'; import { SettingService } from '@/setting/services/setting.service'; @@ -41,6 +42,7 @@ export class BotService { private readonly conversationService: ConversationService, private readonly subscriberService: SubscriberService, private readonly settingService: SettingService, + private readonly helperService: HelperService, ) {} /** @@ -243,6 +245,49 @@ export class BotService { } } + /** + * Handles advancing the conversation to the specified *next* block. + * + * 1. Updates “popular blocks” stats. + * 2. Persists the updated conversation context. + * 3. Triggers the next block. + * 4. Ends the conversation if an unrecoverable error occurs. + */ + async proceedToNextBlock( + convo: ConversationFull, + next: BlockFull, + event: EventWrapper, + fallback: boolean, + ): Promise { + // Increment stats about popular blocks + this.eventEmitter.emit('hook:stats:entry', BotStatsType.popular, next.name); + this.logger.debug( + 'Proceeding to next block ', + next.id, + ' for conversation ', + convo.id, + ); + + try { + convo.context.attempt = fallback ? convo.context.attempt + 1 : 0; + const updatedConversation = + await this.conversationService.storeContextData( + convo, + next, + event, + // If this is a local fallback then we don’t capture vars. + !fallback, + ); + + await this.triggerBlock(event, updatedConversation, next, fallback); + return true; + } catch (err) { + this.logger.error('Unable to proceed to the next block!', err); + this.eventEmitter.emit('hook:conversation:end', convo); + return false; + } + } + /** * Processes and responds to an incoming message within an ongoing conversation flow. * Determines the next block in the conversation, attempts to match the message with available blocks, @@ -283,7 +328,7 @@ export class BotService { ); // If there is no match in next block then loopback (current fallback) // This applies only to text messages + there's a max attempt to be specified - let fallbackBlock: BlockFull | undefined; + let fallbackBlock: BlockFull | undefined = undefined; if ( !matchedBlock && event.getMessageType() === IncomingMessageType.message && @@ -303,11 +348,7 @@ export class BotService { category: null, previousBlocks: [], }; - convo.context.attempt++; fallback = true; - } else { - convo.context.attempt = 0; - fallbackBlock = undefined; } const next = matchedBlock || fallbackBlock; @@ -315,30 +356,8 @@ export class BotService { this.logger.debug('Responding ...', convo.id); if (next) { - // Increment stats about popular blocks - this.eventEmitter.emit( - 'hook:stats:entry', - BotStatsType.popular, - next.name, - ); - // Go next! - this.logger.debug('Respond to nested conversion! Go next ', next.id); - try { - const updatedConversation = - await this.conversationService.storeContextData( - convo, - next, - event, - // If this is a local fallback then we don't capture vars - // Otherwise, old captured const value may be replaced by another const value - !fallback, - ); - await this.triggerBlock(event, updatedConversation, next, fallback); - } catch (err) { - this.logger.error('Unable to store context data!', err); - return this.eventEmitter.emit('hook:conversation:end', convo); - } - return true; + // Proceed to the execution of the next block + return await this.proceedToNextBlock(convo, next, event, fallback); } else { // Conversation is still active, but there's no matching block to call next // We'll end the conversation but this message is probably lost in time and space. From b973c466d741d561faaa917649283c5020a6dd8f Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Wed, 11 Jun 2025 16:44:14 +0100 Subject: [PATCH 2/3] test: consolidate unit tests --- api/src/chat/services/bot.service.spec.ts | 423 ++++++++++++++-------- api/src/chat/services/bot.service.ts | 41 ++- 2 files changed, 298 insertions(+), 166 deletions(-) diff --git a/api/src/chat/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index 9fb7fa87..0e4643f0 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -51,6 +51,8 @@ import { SettingService } from '@/setting/services/setting.service'; import { installBlockFixtures } from '@/utils/test/fixtures/block'; import { installContentFixtures } from '@/utils/test/fixtures/content'; import { installSubscriberFixtures } from '@/utils/test/fixtures/subscriber'; +import { mockWebChannelData, textBlock } from '@/utils/test/mocks/block'; +import { conversationGetStarted } from '@/utils/test/mocks/conversation'; import { closeInMongodConnection, rootMongooseTestModule, @@ -212,157 +214,149 @@ describe('BotService', () => { afterEach(jest.clearAllMocks); afterAll(closeInMongodConnection); - - it('should start a conversation', async () => { - const triggeredEvents: any[] = []; - - eventEmitter.on('hook:stats:entry', (...args) => { - triggeredEvents.push(args); + describe('startConversation', () => { + afterAll(() => { + jest.restoreAllMocks(); }); - const event = new WebEventWrapper(handler, webEventText, { - isSocket: false, - ipAddress: '1.1.1.1', - agent: 'Chromium', - }); + it('should start a conversation', async () => { + const triggeredEvents: any[] = []; - const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] }); - const webSubscriber = (await subscriberService.findOne({ - foreign_id: 'foreign-id-web-1', - }))!; + eventEmitter.on('hook:stats:entry', (...args) => { + triggeredEvents.push(args); + }); - event.setSender(webSubscriber); - - let hasBotSpoken = false; - const clearMock = jest - .spyOn(botService, 'triggerBlock') - .mockImplementation( - ( - actualEvent: WebEventWrapper, - actualConversation: Conversation, - actualBlock: BlockFull, - isFallback: boolean, - ) => { - expect(actualConversation).toEqualPayload({ - sender: webSubscriber.id, - active: true, - next: [], - context: { - user: { - first_name: webSubscriber.first_name, - last_name: webSubscriber.last_name, - language: 'en', - id: webSubscriber.id, - }, - user_location: { - lat: 0, - lon: 0, - }, - skip: {}, - vars: {}, - nlp: null, - payload: null, - attempt: 0, - channel: 'web-channel', - text: webEventText.data.text, - }, - }); - expect(actualEvent).toEqual(event); - expect(actualBlock).toEqual(block); - expect(isFallback).toEqual(false); - hasBotSpoken = true; - }, + const event = new WebEventWrapper( + handler, + webEventText, + mockWebChannelData, ); - await botService.startConversation(event, block); - expect(hasBotSpoken).toEqual(true); - expect(triggeredEvents).toEqual([ - ['popular', 'hasNextBlocks'], - ['new_conversations', 'New conversations'], - ]); - clearMock.mockClear(); - }); + const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] }); + const webSubscriber = (await subscriberService.findOne({ + foreign_id: 'foreign-id-web-1', + }))!; - it('should capture a conversation', async () => { - const triggeredEvents: any[] = []; + event.setSender(webSubscriber); - eventEmitter.on('hook:stats:entry', (...args) => { - triggeredEvents.push(args); - }); - - const event = new WebEventWrapper(handler, webEventText, { - isSocket: false, - ipAddress: '1.1.1.1', - agent: 'Chromium', - }); - const webSubscriber = (await subscriberService.findOne({ - foreign_id: 'foreign-id-web-1', - }))!; - event.setSender(webSubscriber); - - const clearMock = jest - .spyOn(botService, 'handleOngoingConversationMessage') - .mockImplementation( - async ( - actualConversation: ConversationFull, - event: WebEventWrapper, - ) => { - expect(actualConversation).toEqualPayload({ - next: [], - sender: webSubscriber, - active: true, - context: { - user: { - first_name: webSubscriber.first_name, - last_name: webSubscriber.last_name, - language: 'en', - id: webSubscriber.id, + let hasBotSpoken = false; + const clearMock = jest + .spyOn(botService, 'triggerBlock') + .mockImplementation( + ( + actualEvent: WebEventWrapper, + actualConversation: Conversation, + actualBlock: BlockFull, + isFallback: boolean, + ) => { + expect(actualConversation).toEqualPayload({ + sender: webSubscriber.id, + active: true, + next: [], + context: { + user: { + first_name: webSubscriber.first_name, + last_name: webSubscriber.last_name, + language: 'en', + id: webSubscriber.id, + }, + user_location: { + lat: 0, + lon: 0, + }, + skip: {}, + vars: {}, + nlp: null, + payload: null, + attempt: 0, + channel: 'web-channel', + text: webEventText.data.text, }, - user_location: { lat: 0, lon: 0 }, - vars: {}, - skip: {}, - nlp: null, - payload: null, - attempt: 0, - channel: 'web-channel', - text: webEventText.data.text, - }, - }); - expect(event).toEqual(event); - return true; - }, - ); - const captured = await botService.processConversationMessage(event); - expect(captured).toBe(true); - expect(triggeredEvents).toEqual([ - ['existing_conversations', 'Existing conversations'], - ]); - clearMock.mockClear(); + }); + expect(actualEvent).toEqual(event); + expect(actualBlock).toEqual(block); + expect(isFallback).toEqual(false); + hasBotSpoken = true; + }, + ); + + await botService.startConversation(event, block); + expect(hasBotSpoken).toEqual(true); + expect(triggeredEvents).toEqual([ + ['popular', 'hasNextBlocks'], + ['new_conversations', 'New conversations'], + ]); + clearMock.mockClear(); + }); }); - it('has no active conversation', async () => { - const triggeredEvents: any[] = []; - eventEmitter.on('hook:stats:entry', (...args) => { - triggeredEvents.push(args); + describe('processConversationMessage', () => { + afterAll(() => { + jest.restoreAllMocks(); }); - const event = new WebEventWrapper(handler, webEventText, { - isSocket: false, - ipAddress: '1.1.1.1', - agent: 'Chromium', - }); - const webSubscriber = (await subscriberService.findOne({ - foreign_id: 'foreign-id-web-2', - }))!; - event.setSender(webSubscriber); - const captured = await botService.processConversationMessage(event); - expect(captured).toBe(false); - expect(triggeredEvents).toEqual([]); + it('has no active conversation', async () => { + const triggeredEvents: any[] = []; + eventEmitter.on('hook:stats:entry', (...args) => { + triggeredEvents.push(args); + }); + const event = new WebEventWrapper( + handler, + webEventText, + mockWebChannelData, + ); + const webSubscriber = (await subscriberService.findOne({ + foreign_id: 'foreign-id-web-2', + }))!; + event.setSender(webSubscriber); + const captured = await botService.processConversationMessage(event); + + expect(captured).toBe(false); + expect(triggeredEvents).toEqual([]); + }); + + it('should capture a conversation', async () => { + const triggeredEvents: any[] = []; + + eventEmitter.on('hook:stats:entry', (...args) => { + triggeredEvents.push(args); + }); + + const event = new WebEventWrapper( + handler, + webEventText, + mockWebChannelData, + ); + const webSubscriber = (await subscriberService.findOne({ + foreign_id: 'foreign-id-web-1', + }))!; + event.setSender(webSubscriber); + + jest + .spyOn(botService, 'handleOngoingConversationMessage') + .mockImplementation(() => Promise.resolve(true)); + const captured = await botService.processConversationMessage(event); + expect(captured).toBe(true); + expect(triggeredEvents).toEqual([ + ['existing_conversations', 'Existing conversations'], + ]); + }); }); describe('proceedToNextBlock', () => { + const mockEvent = new WebEventWrapper( + handler, + webEventText, + mockWebChannelData, + ); + + afterAll(() => { + jest.restoreAllMocks(); + }); + it('should emit stats and call triggerBlock, returning true on success and reset attempt if not fallback', async () => { - const convo = { + const mockConvo = { + ...conversationGetStarted, id: 'convo1', context: { attempt: 2 }, next: [], @@ -370,23 +364,20 @@ describe('BotService', () => { active: true, } as unknown as ConversationFull; const next = { id: 'block1', name: 'Block 1' } as BlockFull; - const event = {} as any; const fallback = false; jest .spyOn(conversationService, 'storeContextData') - .mockImplementation((convo, _next, _event, _captureVars) => { - return Promise.resolve({ - ...convo, - } as Conversation); + .mockImplementation(() => { + return Promise.resolve(mockConvo as unknown as Conversation); }); jest.spyOn(botService, 'triggerBlock').mockResolvedValue(undefined); const emitSpy = jest.spyOn(eventEmitter, 'emit'); const result = await botService.proceedToNextBlock( - convo, + mockConvo, next, - event, + mockEvent, fallback, ); @@ -397,48 +388,48 @@ describe('BotService', () => { ); expect(botService.triggerBlock).toHaveBeenCalledWith( - event, + mockEvent, expect.objectContaining({ id: 'convo1' }), next, fallback, ); expect(result).toBe(true); - expect(convo.context.attempt).toBe(0); + expect(mockConvo.context.attempt).toBe(0); }); it('should increment attempt if fallback is true', async () => { - const convo = { + const mockConvo = { + ...conversationGetStarted, id: 'convo2', context: { attempt: 1 }, next: [], sender: 'user2', active: true, - } as any; + } as unknown as ConversationFull; const next = { id: 'block2', name: 'Block 2' } as any; - const event = {} as any; const fallback = true; const result = await botService.proceedToNextBlock( - convo, + mockConvo, next, - event, + mockEvent, fallback, ); - expect(convo.context.attempt).toBe(2); + expect(mockConvo.context.attempt).toBe(2); expect(result).toBe(true); }); it('should handle errors and emit conversation:end, returning false', async () => { - const convo = { + const mockConvo = { + ...conversationGetStarted, id: 'convo3', context: { attempt: 1 }, next: [], sender: 'user3', active: true, - } as any; + } as unknown as ConversationFull; const next = { id: 'block3', name: 'Block 3' } as any; - const event = {} as any; const fallback = false; jest @@ -447,14 +438,136 @@ describe('BotService', () => { const emitSpy = jest.spyOn(eventEmitter, 'emit'); const result = await botService.proceedToNextBlock( - convo, + mockConvo, next, - event, + mockEvent, fallback, ); - expect(emitSpy).toHaveBeenCalledWith('hook:conversation:end', convo); + expect(emitSpy).toHaveBeenCalledWith('hook:conversation:end', mockConvo); expect(result).toBe(false); }); }); + + describe('handleOngoingConversationMessage', () => { + const mockConvo = { + ...conversationGetStarted, + id: 'convo1', + context: { ...conversationGetStarted.context, attempt: 0 }, + next: [{ id: 'block1' }], + current: { + ...conversationGetStarted.current, + id: 'block0', + options: { + ...conversationGetStarted.current.options, + fallback: { + active: true, + max_attempts: 2, + message: [], + }, + }, + }, + } as unknown as ConversationFull; + + const mockEvent = new WebEventWrapper( + handler, + webEventText, + mockWebChannelData, + ); + + beforeAll(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should proceed to the matched next block', async () => { + const matchedBlock = { + ...textBlock, + id: 'block1', + name: 'Block 1', + } as BlockFull; + jest + .spyOn(blockService, 'findAndPopulate') + .mockResolvedValue([matchedBlock]); + jest.spyOn(blockService, 'match').mockResolvedValue(matchedBlock); + jest.spyOn(botService, 'proceedToNextBlock').mockResolvedValue(true); + + const result = await botService.handleOngoingConversationMessage( + mockConvo, + mockEvent, + ); + + expect(blockService.findAndPopulate).toHaveBeenCalled(); + expect(blockService.match).toHaveBeenCalled(); + expect(botService.proceedToNextBlock).toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it('should proceed to fallback block if no match and fallback is allowed', async () => { + jest.spyOn(blockService, 'findAndPopulate').mockResolvedValue([]); + jest.spyOn(blockService, 'match').mockResolvedValue(undefined); + const proceedSpy = jest + .spyOn(botService, 'proceedToNextBlock') + .mockResolvedValue(true); + + const result = await botService.handleOngoingConversationMessage( + mockConvo, + mockEvent, + ); + + expect(proceedSpy).toHaveBeenCalledWith( + mockConvo, + expect.objectContaining({ id: 'block0', nextBlocks: mockConvo.next }), + mockEvent, + true, + ); + expect(result).toBe(true); + }); + + it('should end conversation and return false if no match and fallback not allowed', async () => { + const mockConvoWithoutFallback = { + ...mockConvo, + current: { + ...mockConvo.current, + options: { + ...mockConvo.current.options, + fallback: { + active: false, + max_attempts: 2, + message: [], + }, + }, + }, + } as unknown as ConversationFull; + jest.spyOn(blockService, 'findAndPopulate').mockResolvedValue([]); + jest.spyOn(blockService, 'match').mockResolvedValue(undefined); + const emitSpy = jest.spyOn(eventEmitter, 'emit'); + + const result = await botService.handleOngoingConversationMessage( + mockConvoWithoutFallback, + mockEvent, + ); + + expect(emitSpy).toHaveBeenCalledWith( + 'hook:conversation:end', + mockConvoWithoutFallback, + ); + expect(result).toBe(false); + }); + + it('should end conversation and throw if an error occurs', async () => { + jest + .spyOn(blockService, 'findAndPopulate') + .mockRejectedValue(new Error('fail')); + const emitSpy = jest.spyOn(eventEmitter, 'emit'); + + await expect( + botService.handleOngoingConversationMessage(mockConvo, mockEvent), + ).rejects.toThrow('fail'); + expect(emitSpy).toHaveBeenCalledWith('hook:conversation:end', mockConvo); + }); + }); }); diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index 4007d70e..304e2bc2 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -28,6 +28,7 @@ import { OutgoingMessageFormat, StdOutgoingMessageEnvelope, } from '../schemas/types/message'; +import { BlockOptions } from '../schemas/types/options'; import { BlockService } from './block.service'; import { ConversationService } from './conversation.service'; @@ -288,6 +289,27 @@ export class BotService { } } + /** + * Determines if a fallback should be attempted based on the event type, fallback options, and conversation context. + * + * @param event - The incoming event that triggered the conversation flow. + * @param fallbackOptions - The options for fallback behavior defined in the block. + * @param convo - The current conversation object containing context and state. + * + * @returns A boolean indicating whether a fallback should be attempted. + */ + private shouldAttemptLocalFallback( + event: EventWrapper, + fallbackOptions: BlockOptions['fallback'], + convo: ConversationFull, + ): boolean { + return ( + event.getMessageType() === IncomingMessageType.message && + !!fallbackOptions?.active && + convo.context.attempt < (fallbackOptions?.max_attempts ?? 0) + ); + } + /** * Processes and responds to an incoming message within an ongoing conversation flow. * Determines the next block in the conversation, attempts to match the message with available blocks, @@ -302,25 +324,25 @@ export class BotService { convo: ConversationFull, event: EventWrapper, ) { - const nextIds = convo.next.map(({ id }) => id); - // Reload blocks in order to populate his nextBlocks - // nextBlocks & trigger/assign _labels try { - const nextBlocks = await this.blockService.findAndPopulate({ - _id: { $in: nextIds }, - }); let fallback = false; - const fallbackOptions = convo.current?.options?.fallback + const currentBlock = convo.current; + const fallbackOptions: BlockOptions['fallback'] = convo.current?.options + ?.fallback ? convo.current.options.fallback : { active: false, max_attempts: 0, + message: [], }; // We will avoid having multiple matches when we are not at the start of a conversation // and only if local fallback is enabled const canHaveMultipleMatches = !fallbackOptions.active; // Find the next block that matches + const nextBlocks = await this.blockService.findAndPopulate({ + _id: { $in: convo.next.map(({ id }) => id) }, + }); const matchedBlock = await this.blockService.match( nextBlocks, event, @@ -331,13 +353,10 @@ export class BotService { let fallbackBlock: BlockFull | undefined = undefined; if ( !matchedBlock && - event.getMessageType() === IncomingMessageType.message && - fallbackOptions.active && - convo.context.attempt < fallbackOptions.max_attempts + this.shouldAttemptLocalFallback(event, fallbackOptions, convo) ) { // Trigger block fallback // NOTE : current is not populated, this may cause some anomaly - const currentBlock = convo.current; fallbackBlock = { ...currentBlock, nextBlocks: convo.next, From 39287c0fc9df04169c12ed4b6cca60f5b79737ce Mon Sep 17 00:00:00 2001 From: Mohamed Marrouchi Date: Wed, 11 Jun 2025 16:46:34 +0100 Subject: [PATCH 3/3] fix: remove unused dep injection --- api/src/chat/services/bot.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/chat/services/bot.service.ts b/api/src/chat/services/bot.service.ts index 304e2bc2..7052d961 100644 --- a/api/src/chat/services/bot.service.ts +++ b/api/src/chat/services/bot.service.ts @@ -11,7 +11,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { BotStatsType } from '@/analytics/schemas/bot-stats.schema'; import EventWrapper from '@/channel/lib/EventWrapper'; -import { HelperService } from '@/helper/helper.service'; import { LoggerService } from '@/logger/logger.service'; import { SettingService } from '@/setting/services/setting.service'; @@ -43,7 +42,6 @@ export class BotService { private readonly conversationService: ConversationService, private readonly subscriberService: SubscriberService, private readonly settingService: SettingService, - private readonly helperService: HelperService, ) {} /**