diff --git a/api/src/chat/controllers/block.controller.spec.ts b/api/src/chat/controllers/block.controller.spec.ts index 426a19b3..16e1754b 100644 --- a/api/src/chat/controllers/block.controller.spec.ts +++ b/api/src/chat/controllers/block.controller.spec.ts @@ -20,7 +20,6 @@ import { LanguageRepository } from '@/i18n/repositories/language.repository'; import { LanguageModel } from '@/i18n/schemas/language.schema'; import { I18nService } from '@/i18n/services/i18n.service'; import { LanguageService } from '@/i18n/services/language.service'; -import { LoggerService } from '@/logger/logger.service'; import { NlpEntityRepository } from '@/nlp/repositories/nlp-entity.repository'; import { NlpSampleEntityRepository } from '@/nlp/repositories/nlp-sample-entity.repository'; import { NlpValueRepository } from '@/nlp/repositories/nlp-value.repository'; @@ -128,7 +127,6 @@ describe('BlockController', () => { PermissionService, LanguageService, PluginService, - LoggerService, NlpEntityService, NlpEntityRepository, NlpSampleEntityRepository, diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index d0afb2d5..076c6a07 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -395,22 +395,22 @@ export class BlockService extends BaseService< const nlpCacheMap = await this.entityService.getNlpMap(); // @TODO Make nluPenaltyFactor configurable in UI settings const nluPenaltyFactor = 0.95; - // Compute individual pattern scores using the cache - const patternScores: number[] = patterns.map((pattern) => { - const entityData = nlpCacheMap.get(pattern.entity); - if (!entityData) return 0; + const patternScores: number[] = patterns + .filter(({ entity }) => nlpCacheMap.has(entity)) + .map((pattern) => { + const entityData = nlpCacheMap.get(pattern.entity); - const matchedEntity: NLU.ParseEntity | undefined = nlp.entities.find( - (e) => this.matchesEntityData(e, pattern, entityData), - ); + const matchedEntity: NLU.ParseEntity | undefined = nlp.entities.find( + (e) => this.matchesEntityData(e, pattern, entityData!), + ); - return this.computePatternScore( - matchedEntity, - pattern, - entityData, - nluPenaltyFactor, - ); - }); + return this.computePatternScore( + matchedEntity, + pattern, + entityData!, + nluPenaltyFactor, + ); + }); // Sum the scores return patternScores.reduce((sum, score) => sum + score, 0); diff --git a/api/src/nlp/controllers/nlp-entity.controller.spec.ts b/api/src/nlp/controllers/nlp-entity.controller.spec.ts index 6a0189bb..fd70a91b 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.spec.ts @@ -9,6 +9,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { BadRequestException, + ConflictException, MethodNotAllowedException, NotFoundException, } from '@nestjs/common'; @@ -278,7 +279,7 @@ describe('NlpEntityController', () => { }; await expect( nlpEntityController.updateOne(buitInEntityId!, updateNlpEntity), - ).rejects.toThrow(MethodNotAllowedException); + ).rejects.toThrow(ConflictException); }); it('should update weight if entity is builtin and weight is provided', async () => { diff --git a/api/src/nlp/controllers/nlp-entity.controller.ts b/api/src/nlp/controllers/nlp-entity.controller.ts index a801ff15..af549526 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.ts @@ -9,6 +9,7 @@ import { BadRequestException, Body, + ConflictException, Controller, Delete, Get, @@ -166,7 +167,7 @@ export class NlpEntityController extends BaseController< updateNlpEntityDto.weight, ); } else { - throw new MethodNotAllowedException( + throw new ConflictException( `Cannot update builtin NLP Entity ${nlpEntity.name} except for weight`, ); } diff --git a/api/src/nlp/dto/nlp-entity.dto.ts b/api/src/nlp/dto/nlp-entity.dto.ts index c5d0d58b..86ddf7d8 100644 --- a/api/src/nlp/dto/nlp-entity.dto.ts +++ b/api/src/nlp/dto/nlp-entity.dto.ts @@ -16,7 +16,7 @@ import { IsOptional, IsString, Matches, - Min, + Validate, } from 'class-validator'; import { DtoConfig } from '@/utils/types/dto.types'; @@ -55,8 +55,10 @@ export class NlpEntityCreateDto { type: Number, }) @IsOptional() - @Min(0.01, { message: 'Weight must be positive' }) - @IsNumber() + @Validate((value) => value > 0, { + message: 'Weight must be a strictly positive number', + }) + @IsNumber({ allowNaN: false, allowInfinity: false }) weight?: number; } diff --git a/api/src/nlp/schemas/nlp-entity.schema.ts b/api/src/nlp/schemas/nlp-entity.schema.ts index 0c61c0c8..a5879d5d 100644 --- a/api/src/nlp/schemas/nlp-entity.schema.ts +++ b/api/src/nlp/schemas/nlp-entity.schema.ts @@ -61,7 +61,14 @@ export class NlpEntityStub extends BaseSchema { /** * Entity's weight used to determine the next block to trigger in the conversational flow. */ - @Prop({ type: Number, default: 1, min: 0 }) + @Prop({ + type: Number, + default: 1, + validate: { + validator: (value: number) => value > 0, + message: 'Weight must be a strictly positive number', + }, + }) weight: number; /** diff --git a/api/src/nlp/services/nlp-entity.service.spec.ts b/api/src/nlp/services/nlp-entity.service.spec.ts index 5b11e52a..d90e6ff9 100644 --- a/api/src/nlp/services/nlp-entity.service.spec.ts +++ b/api/src/nlp/services/nlp-entity.service.spec.ts @@ -57,6 +57,8 @@ describe('nlpEntityService', () => { provide: CACHE_MANAGER, useValue: { del: jest.fn(), + set: jest.fn(), + get: jest.fn(), }, }, ], @@ -175,7 +177,7 @@ describe('nlpEntityService', () => { await expect( nlpEntityService.updateWeight(createdEntity.id, invalidWeight), - ).rejects.toThrow('Weight must be a positive number'); + ).rejects.toThrow('Weight must be a strictly positive number'); }); afterEach(async () => { diff --git a/api/src/nlp/services/nlp-entity.service.ts b/api/src/nlp/services/nlp-entity.service.ts index 907021b9..c92b7888 100644 --- a/api/src/nlp/services/nlp-entity.service.ts +++ b/api/src/nlp/services/nlp-entity.service.ts @@ -63,15 +63,11 @@ export class NlpEntityService extends BaseService< * @returns A promise that resolves to the updated entity. */ async updateWeight(id: string, updatedWeight: number): Promise { - if (updatedWeight < 0) { - throw new Error('Weight must be a positive number'); + if (updatedWeight <= 0) { + throw new Error('Weight must be a strictly positive number'); } - return await this.repository.updateOne( - id, - { weight: updatedWeight }, - { new: true }, - ); + return await this.repository.updateOne(id, { weight: updatedWeight }); } /** @@ -139,7 +135,11 @@ export class NlpEntityService extends BaseService< */ @OnEvent('hook:nlpEntity:*') async handleNlpEntityUpdateEvent() { - this.clearCache(); + try { + await this.clearCache(); + } catch (error) { + this.logger.error('Failed to clear NLP entity cache', error); + } } /** @@ -148,7 +148,11 @@ export class NlpEntityService extends BaseService< */ @OnEvent('hook:nlpValue:*') async handleNlpValueUpdateEvent() { - this.clearCache(); + try { + await this.clearCache(); + } catch (error) { + this.logger.error('Failed to clear NLP value cache', error); + } } /** diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 92273736..f3633259 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -123,7 +123,7 @@ "video_error": "Video not found", "missing_fields_error": "Please make sure that all required fields are filled", "weight_required_error": "Weight is required or invalid", - "weight_positive_number_error": "Weight must be a positive number" + "weight_positive_number_error": "Weight must be a strictly positive number" }, "menu": { "terms": "Terms of Use", diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json index 15982b1d..5ff646ea 100644 --- a/frontend/public/locales/fr/translation.json +++ b/frontend/public/locales/fr/translation.json @@ -122,7 +122,7 @@ "audio_error": "Audio introuvable", "video_error": "Vidéo introuvable", "missing_fields_error": "Veuillez vous assurer que tous les champs sont remplis correctement", - "weight_positive_number_error": "Le poids doit être un nombre positif", + "weight_positive_number_error": "Le poids doit être un nombre strictement positif", "weight_required_error": "Le poids est requis ou bien invalide" }, "menu": { diff --git a/frontend/src/components/nlp/components/NlpEntityForm.tsx b/frontend/src/components/nlp/components/NlpEntityForm.tsx index 88f8c079..1fd49c84 100644 --- a/frontend/src/components/nlp/components/NlpEntityForm.tsx +++ b/frontend/src/components/nlp/components/NlpEntityForm.tsx @@ -145,16 +145,15 @@ export const NlpEntityVarForm: FC> = ({ message: t("message.weight_positive_number_error"), }, validate: (value) => - value && value! > 0 + value && value > 0 ? true : t("message.weight_positive_number_error"), })} type="number" inputProps={{ - min: 0.01, + min: 0, step: 0.01, inputMode: "numeric", - pattern: "[1-9][0-9]*", }} error={!!errors.weight} helperText={errors.weight?.message}