Skip to content

Commit ac74a83

Browse files
author
MohamedAliBouhaouala
committed
fix: restore files
1 parent fab7a63 commit ac74a83

File tree

5 files changed

+145
-169
lines changed

5 files changed

+145
-169
lines changed

api/src/chat/chat.module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/*
2-
* Copyright © 2025 Hexastack. All rights reserved.
32
* Copyright © 2025 Hexastack. All rights reserved.
43
*
54
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
@@ -12,6 +11,9 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
1211
import { MongooseModule } from '@nestjs/mongoose';
1312

1413
import { AttachmentModule } from '@/attachment/attachment.module';
14+
import { AttachmentRepository } from '@/attachment/repositories/attachment.repository';
15+
import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
16+
import { AttachmentService } from '@/attachment/services/attachment.service';
1517
import { ChannelModule } from '@/channel/channel.module';
1618
import { CmsModule } from '@/cms/cms.module';
1719
import { NlpModule } from '@/nlp/nlp.module';
@@ -60,6 +62,7 @@ import { SubscriberService } from './services/subscriber.service';
6062
SubscriberModel,
6163
ConversationModel,
6264
SubscriberModel,
65+
AttachmentModel,
6366
]),
6467
forwardRef(() => ChannelModule),
6568
CmsModule,
@@ -95,6 +98,8 @@ import { SubscriberService } from './services/subscriber.service';
9598
ConversationService,
9699
ChatService,
97100
BotService,
101+
AttachmentService,
102+
AttachmentRepository,
98103
],
99104
exports: [
100105
SubscriberService,

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

Lines changed: 28 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { NlpValueRepository } from '@/nlp/repositories/nlp-value.repository';
3737
import { NlpEntityModel } from '@/nlp/schemas/nlp-entity.schema';
3838
import { NlpSampleEntityModel } from '@/nlp/schemas/nlp-sample-entity.schema';
3939
import { NlpValueModel } from '@/nlp/schemas/nlp-value.schema';
40+
import { NlpCacheMap } from '@/nlp/schemas/types';
4041
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
4142
import { NlpValueService } from '@/nlp/services/nlp-value.service';
4243
import { PluginService } from '@/plugins/plugins.service';
@@ -51,8 +52,11 @@ import {
5152
blockGetStarted,
5253
blockProductListMock,
5354
blocks,
55+
mockModifiedNlpBlock,
5456
mockNlpBlock,
55-
nlpBlocks,
57+
mockNlpPatternsSetOne,
58+
mockNlpPatternsSetThree,
59+
mockNlpPatternsSetTwo,
5660
} from '@/utils/test/mocks/block';
5761
import {
5862
contextBlankInstance,
@@ -81,25 +85,6 @@ import { CategoryRepository } from './../repositories/category.repository';
8185
import { BlockService } from './block.service';
8286
import { CategoryService } from './category.service';
8387

84-
// Create a mock for the NlpEntityService
85-
const mockNlpEntityService = {
86-
findOne: jest.fn().mockImplementation((query) => {
87-
if (query.name === 'intent') {
88-
return Promise.resolve({
89-
lookups: ['trait'],
90-
id: '67e3e41eff551ca5be70559c',
91-
});
92-
}
93-
if (query.name === 'firstname') {
94-
return Promise.resolve({
95-
lookups: ['trait'],
96-
id: '67e3e41eff551ca5be70559d',
97-
});
98-
}
99-
return Promise.resolve(null); // Default response if the entity isn't found
100-
}),
101-
};
102-
10388
describe('BlockService', () => {
10489
let blockRepository: BlockRepository;
10590
let categoryRepository: CategoryRepository;
@@ -110,8 +95,6 @@ describe('BlockService', () => {
11095
let contentService: ContentService;
11196
let contentTypeService: ContentTypeService;
11297
let nlpEntityService: NlpEntityService;
113-
let settingService: SettingService;
114-
let settings: Settings;
11598

11699
beforeAll(async () => {
117100
const { getMocks } = await buildTestingMocks({
@@ -129,8 +112,8 @@ describe('BlockService', () => {
129112
LabelModel,
130113
LanguageModel,
131114
NlpEntityModel,
132-
NlpValueModel,
133115
NlpSampleEntityModel,
116+
NlpValueModel,
134117
]),
135118
],
136119
providers: [
@@ -140,19 +123,19 @@ describe('BlockService', () => {
140123
ContentRepository,
141124
AttachmentRepository,
142125
LanguageRepository,
143-
NlpEntityRepository,
144-
NlpSampleEntityRepository,
145-
NlpValueRepository,
146126
BlockService,
147127
CategoryService,
148128
ContentTypeService,
149129
ContentService,
150130
AttachmentService,
151131
LanguageService,
152-
NlpValueService,
132+
NlpEntityRepository,
133+
NlpValueRepository,
134+
NlpSampleEntityRepository,
135+
NlpEntityService,
153136
{
154-
provide: NlpEntityService, // Mocking NlpEntityService
155-
useValue: mockNlpEntityService,
137+
provide: NlpValueService,
138+
useValue: {},
156139
},
157140
{
158141
provide: PluginService,
@@ -186,14 +169,22 @@ describe('BlockService', () => {
186169
},
187170
},
188171
],
189-
}).compile();
190-
blockService = module.get<BlockService>(BlockService);
191-
contentService = module.get<ContentService>(ContentService);
192-
settingService = module.get<SettingService>(SettingService);
193-
contentTypeService = module.get<ContentTypeService>(ContentTypeService);
194-
categoryRepository = module.get<CategoryRepository>(CategoryRepository);
195-
blockRepository = module.get<BlockRepository>(BlockRepository);
196-
nlpEntityService = module.get<NlpEntityService>(NlpEntityService);
172+
});
173+
[
174+
blockService,
175+
contentService,
176+
contentTypeService,
177+
categoryRepository,
178+
blockRepository,
179+
nlpEntityService,
180+
] = await getMocks([
181+
BlockService,
182+
ContentService,
183+
ContentTypeService,
184+
CategoryRepository,
185+
BlockRepository,
186+
NlpEntityService,
187+
]);
197188
category = (await categoryRepository.findOne({ label: 'default' }))!;
198189
hasPreviousBlocks = (await blockRepository.findOne({
199190
name: 'hasPreviousBlocks',
@@ -554,59 +545,6 @@ describe('BlockService', () => {
554545
});
555546
});
556547

557-
describe('matchBestNLP', () => {
558-
it('should return undefined if blocks is empty', async () => {
559-
const result = await blockService.matchBestNLP([]);
560-
expect(result).toBeUndefined();
561-
});
562-
563-
it('should return the only block if there is one', async () => {
564-
const result = await blockService.matchBestNLP([blockEmpty]);
565-
expect(result).toBe(blockEmpty);
566-
});
567-
568-
it('should correctly select the best block based on NLP scores', async () => {
569-
const result = await blockService.matchBestNLP(nlpBlocks);
570-
expect(result).toBe(mockNlpBlock);
571-
572-
// Iterate over each block
573-
for (const block of nlpBlocks) {
574-
// Flatten the patterns array and filter valid NLP patterns
575-
block.patterns
576-
.flatMap((pattern) => (Array.isArray(pattern) ? pattern : []))
577-
.filter((p) => typeof p === 'object' && 'entity' in p && 'match' in p) // Filter only valid patterns with entity and match
578-
.forEach((p) => {
579-
// Check if findOne was called with the correct entity
580-
expect(nlpEntityService.findOne).toHaveBeenCalledWith(
581-
{ name: p.entity },
582-
undefined,
583-
{ _id: 0, lookups: 1 },
584-
);
585-
});
586-
}
587-
});
588-
589-
it('should return the block with the highest combined score', async () => {
590-
const result = await blockService.matchBestNLP(nlpBlocks);
591-
expect(result).toBe(mockNlpBlock);
592-
// Iterate over each block
593-
for (const block of nlpBlocks) {
594-
// Flatten the patterns array and filter valid NLP patterns
595-
block.patterns
596-
.flatMap((pattern) => (Array.isArray(pattern) ? pattern : []))
597-
.filter((p) => typeof p === 'object' && 'entity' in p && 'match' in p) // Filter only valid patterns with entity and match
598-
.forEach((p) => {
599-
// Check if findOne was called with the correct entity
600-
expect(nlpEntityService.findOne).toHaveBeenCalledWith(
601-
{ name: p.entity },
602-
undefined,
603-
{ _id: 0, lookups: 1 },
604-
);
605-
});
606-
}
607-
});
608-
});
609-
610548
describe('matchText', () => {
611549
it('should return false for matching an empty text', () => {
612550
const result = blockService.matchText('', blockGetStarted);

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

Lines changed: 45 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CONSOLE_CHANNEL_NAME } from '@/extensions/channels/console/settings';
1616
import { NLU } from '@/helper/types';
1717
import { I18nService } from '@/i18n/services/i18n.service';
1818
import { LanguageService } from '@/i18n/services/language.service';
19+
import { NlpCacheMap } from '@/nlp/schemas/types';
1920
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
2021
import { PluginService } from '@/plugins/plugins.service';
2122
import { PluginType } from '@/plugins/types';
@@ -37,6 +38,7 @@ import {
3738
StdOutgoingSystemEnvelope,
3839
} from '../schemas/types/message';
3940
import {
41+
isNlpPattern,
4042
NlpPattern,
4143
NlpPatternMatchResult,
4244
PayloadPattern,
@@ -376,77 +378,62 @@ export class BlockService extends BaseService<
376378
}
377379

378380
/**
379-
* Identifies and returns the best-matching block based on NLP entity scores.
381+
* Selects the best-matching block based on NLP pattern scoring.
380382
*
381-
* This function evaluates a list of blocks by analyzing their associated NLP entities
382-
* and scoring them based on predefined lookup entities. The block with the highest
383-
* score is selected as the best match.
384-
* @param blocks - Blocks on which to perform the filtering
383+
* This function evaluates each block by calculating a score derived from its matched NLP patterns,
384+
* the parsed NLP entities, and a penalty factor. It compares the scores across all blocks and
385+
* returns the one with the highest calculated score.
385386
*
386-
* @returns The best block
387+
* @param blocks - An array of candidate blocks to evaluate.
388+
* @param matchedPatterns - A two-dimensional array of matched NLP patterns corresponding to each block.
389+
* @param nlp - The parsed NLP entities used for scoring.
390+
* @param nlpPenaltyFactor - A numeric penalty factor applied during scoring to influence block selection.
391+
* @returns The block with the highest NLP score, or undefined if no valid block is found.
387392
*/
388393
async matchBestNLP(
389-
blocks: Block[] | BlockFull[] | undefined,
394+
blocks: (Block | BlockFull)[] | undefined,
395+
matchedPatterns: NlpPattern[][],
396+
nlp: NLU.ParseEntities,
397+
nlpPenaltyFactor: number,
390398
): Promise<Block | BlockFull | undefined> {
391-
// @TODO make lookup scores configurable in hexabot settings
392-
const lookupScores: { [key: string]: number } = {
393-
trait: 2,
394-
keywords: 1,
395-
};
396-
397-
// No blocks to check against
398-
if (blocks?.length === 0 || !blocks) {
399-
return undefined;
400-
}
399+
if (!blocks || blocks.length === 0) return undefined;
400+
if (blocks.length === 1) return blocks[0];
401401

402-
// If there's only one block, return it immediately.
403-
if (blocks.length === 1) {
404-
return blocks[0];
405-
}
406402
let bestBlock: Block | BlockFull | undefined;
407403
let highestScore = 0;
408-
409-
// Iterate over each block in blocks
410-
for (const block of blocks) {
411-
let nlpScore = 0;
412-
413-
// Gather all entity lookups for patterns that include an entity
414-
const entityLookups = await Promise.all(
415-
block.patterns
416-
.flatMap((pattern) => (Array.isArray(pattern) ? pattern : []))
417-
.filter((p) => typeof p === 'object' && 'entity' in p && 'match' in p)
418-
.map(async (pattern) => {
419-
const entityName = pattern.entity;
420-
return await this.entityService.findOne(
421-
{ name: entityName },
422-
undefined,
423-
{ lookups: 1, _id: 0 },
424-
);
425-
}),
426-
);
427-
428-
nlpScore += entityLookups.reduce((score, entityLookup) => {
429-
if (
430-
entityLookup &&
431-
entityLookup.lookups[0] &&
432-
lookupScores[entityLookup.lookups[0]]
433-
) {
434-
return score + lookupScores[entityLookup.lookups[0]]; // Add points based on the lookup type
404+
const entityNames: string[] = blocks.flatMap((block) =>
405+
block.patterns.flatMap((patternGroup) => {
406+
if (Array.isArray(patternGroup)) {
407+
return patternGroup.flatMap((pattern) =>
408+
isNlpPattern(pattern) ? [pattern.entity] : [],
409+
);
435410
}
436-
return score; // Return the current score if no match
437-
}, 0);
438-
439-
// Update the best block if the current block has a higher NLP score
411+
return []; // Skip non-array patternGroups
412+
}),
413+
);
414+
const uniqueEntityNames: string[] = [...new Set(entityNames)];
415+
const nlpCacheMap: NlpCacheMap =
416+
await this.entityService.getNlpMap(uniqueEntityNames);
417+
// Iterate through all blocks and calculate their NLP score
418+
for (let i = 0; i < blocks.length; i++) {
419+
const block = blocks[i];
420+
const patterns = matchedPatterns[i];
421+
// If compatible, calculate the NLP score for this block
422+
const nlpScore: number = this.calculateBlockScore(
423+
patterns,
424+
nlp,
425+
nlpCacheMap,
426+
nlpPenaltyFactor,
427+
);
440428
if (nlpScore > highestScore) {
441429
highestScore = nlpScore;
442430
bestBlock = block;
443431
}
444432
}
445433

446-
this.logger.debug(`Best Nlp Score obtained ${highestScore}`);
447-
this.logger.debug(
448-
`Best retrieved block based on NLP entities ${JSON.stringify(bestBlock)}`,
449-
);
434+
this.logger.debug(`Best NLP score obtained: ${highestScore}`);
435+
this.logger.debug(`Best block selected: ${JSON.stringify(bestBlock)}`);
436+
450437
return bestBlock;
451438
}
452439

@@ -465,12 +452,12 @@ export class BlockService extends BaseService<
465452
* @param nlpPenaltyFactor - A multiplier applied to scores when the pattern match type is 'entity'.
466453
* @returns A numeric score representing how well the block matches the given NLP context.
467454
*/
468-
async calculateBlockScore(
455+
calculateBlockScore(
469456
patterns: NlpPattern[],
470457
nlp: NLU.ParseEntities,
471458
nlpCacheMap: NlpCacheMap,
472459
nlpPenaltyFactor: number,
473-
): Promise<number> {
460+
): number {
474461
// Compute individual pattern scores using the cache
475462
const patternScores: number[] = patterns.map((pattern) => {
476463
const entityData = nlpCacheMap.get(pattern.entity);

api/src/i18n/controllers/translation.controller.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/*
2-
* Copyright © 2025 Hexastack. All rights reserved.
32
* Copyright © 2025 Hexastack. All rights reserved.
43
*
54
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:

0 commit comments

Comments
 (0)