mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge pull request #67 from Hexastack/48-request-context-vars-permanent-option
Add permanent option context var
This commit is contained in:
commit
2f2379dd64
@ -100,6 +100,7 @@ describe('ContextVarController', () => {
|
|||||||
const contextVarCreateDto: ContextVarCreateDto = {
|
const contextVarCreateDto: ContextVarCreateDto = {
|
||||||
label: 'contextVarLabel2',
|
label: 'contextVarLabel2',
|
||||||
name: 'test_add',
|
name: 'test_add',
|
||||||
|
permanent: false,
|
||||||
};
|
};
|
||||||
const result = await contextVarController.create(contextVarCreateDto);
|
const result = await contextVarController.create(contextVarCreateDto);
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApiProperty, PartialType } from '@nestjs/swagger';
|
import { ApiProperty, PartialType } from '@nestjs/swagger';
|
||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class ContextVarCreateDto {
|
export class ContextVarCreateDto {
|
||||||
@ApiProperty({ description: 'Context var label', type: String })
|
@ApiProperty({ description: 'Context var label', type: String })
|
||||||
@ -20,6 +20,11 @@ export class ContextVarCreateDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Is context var permanent', type: Boolean })
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
permanent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContextVarUpdateDto extends PartialType(ContextVarCreateDto) {}
|
export class ContextVarUpdateDto extends PartialType(ContextVarCreateDto) {}
|
||||||
|
|||||||
@ -28,6 +28,17 @@ export class ContextVar extends BaseSchema {
|
|||||||
match: /^[a-z_0-9]+$/,
|
match: /^[a-z_0-9]+$/,
|
||||||
})
|
})
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The permanent attribute allows the chatbot to know where to store the context variable.
|
||||||
|
* If the context variable is not permanent, it will be stored in the converation context, which is temporary.
|
||||||
|
* If the context variable is permanent, it will be stored in the subscriber context, which is permanent.
|
||||||
|
*/
|
||||||
|
@Prop({
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
permanent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextVarModel: ModelDefinition = {
|
export const ContextVarModel: ModelDefinition = {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { TFilterPopulateFields } from '@/utils/types/filter.types';
|
|||||||
|
|
||||||
import { Label } from './label.schema';
|
import { Label } from './label.schema';
|
||||||
import { ChannelData } from './types/channel';
|
import { ChannelData } from './types/channel';
|
||||||
|
import { SubscriberContext } from './types/subscriberContext';
|
||||||
|
|
||||||
@Schema({ timestamps: true })
|
@Schema({ timestamps: true })
|
||||||
export class SubscriberStub extends BaseSchema {
|
export class SubscriberStub extends BaseSchema {
|
||||||
@ -107,6 +108,12 @@ export class SubscriberStub extends BaseSchema {
|
|||||||
default: null,
|
default: null,
|
||||||
})
|
})
|
||||||
avatar?: unknown;
|
avatar?: unknown;
|
||||||
|
|
||||||
|
@Prop({
|
||||||
|
type: Object,
|
||||||
|
default: { vars: {} }, //TODO: add this to the migration
|
||||||
|
})
|
||||||
|
context?: SubscriberContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema({ timestamps: true })
|
@Schema({ timestamps: true })
|
||||||
|
|||||||
3
api/src/chat/schemas/types/subscriberContext.ts
Normal file
3
api/src/chat/schemas/types/subscriberContext.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface SubscriberContext {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
@ -52,6 +52,7 @@ import {
|
|||||||
contextBlankInstance,
|
contextBlankInstance,
|
||||||
contextEmailVarInstance,
|
contextEmailVarInstance,
|
||||||
contextGetStartedInstance,
|
contextGetStartedInstance,
|
||||||
|
subscriberContextBlankInstance,
|
||||||
} from '@/utils/test/mocks/conversation';
|
} from '@/utils/test/mocks/conversation';
|
||||||
import { nlpEntitiesGreeting } from '@/utils/test/mocks/nlp';
|
import { nlpEntitiesGreeting } from '@/utils/test/mocks/nlp';
|
||||||
import {
|
import {
|
||||||
@ -69,6 +70,7 @@ import { LabelModel } from '../schemas/label.schema';
|
|||||||
import { FileType } from '../schemas/types/attachment';
|
import { FileType } from '../schemas/types/attachment';
|
||||||
import { Context } from '../schemas/types/context';
|
import { Context } from '../schemas/types/context';
|
||||||
import { PayloadType, StdOutgoingListMessage } from '../schemas/types/message';
|
import { PayloadType, StdOutgoingListMessage } from '../schemas/types/message';
|
||||||
|
import { SubscriberContext } from '../schemas/types/subscriberContext';
|
||||||
|
|
||||||
describe('BlockService', () => {
|
describe('BlockService', () => {
|
||||||
let blockRepository: BlockRepository;
|
let blockRepository: BlockRepository;
|
||||||
@ -436,6 +438,7 @@ describe('BlockService', () => {
|
|||||||
...contextBlankInstance,
|
...contextBlankInstance,
|
||||||
skip: { [blockProductListMock.id]: 0 },
|
skip: { [blockProductListMock.id]: 0 },
|
||||||
},
|
},
|
||||||
|
subscriberContextBlankInstance,
|
||||||
false,
|
false,
|
||||||
'conv_id',
|
'conv_id',
|
||||||
);
|
);
|
||||||
@ -469,6 +472,7 @@ describe('BlockService', () => {
|
|||||||
...contextBlankInstance,
|
...contextBlankInstance,
|
||||||
skip: { [blockProductListMock.id]: 2 },
|
skip: { [blockProductListMock.id]: 2 },
|
||||||
},
|
},
|
||||||
|
subscriberContextBlankInstance,
|
||||||
false,
|
false,
|
||||||
'conv_id',
|
'conv_id',
|
||||||
);
|
);
|
||||||
@ -513,9 +517,20 @@ describe('BlockService', () => {
|
|||||||
skip: { '1': 0 },
|
skip: { '1': 0 },
|
||||||
attempt: 0,
|
attempt: 0,
|
||||||
};
|
};
|
||||||
|
const subscriberContext: SubscriberContext = {
|
||||||
|
...subscriberContextBlankInstance,
|
||||||
|
vars: {
|
||||||
|
phone: '123456789',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
it('should process empty text', () => {
|
it('should process empty text', () => {
|
||||||
const result = blockService.processText('', context, settings);
|
const result = blockService.processText(
|
||||||
|
'',
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
expect(result).toEqual('');
|
expect(result).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -524,6 +539,7 @@ describe('BlockService', () => {
|
|||||||
const result = blockService.processText(
|
const result = blockService.processText(
|
||||||
translation.en,
|
translation.en,
|
||||||
context,
|
context,
|
||||||
|
subscriberContext,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
expect(result).toEqual(translation.fr);
|
expect(result).toEqual(translation.fr);
|
||||||
@ -533,6 +549,7 @@ describe('BlockService', () => {
|
|||||||
const result = blockService.processText(
|
const result = blockService.processText(
|
||||||
'{context.user.first_name} {context.user.last_name}, email : {context.vars.email}',
|
'{context.user.first_name} {context.user.last_name}, email : {context.vars.email}',
|
||||||
contextEmailVarInstance,
|
contextEmailVarInstance,
|
||||||
|
subscriberContext,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
expect(result).toEqual('John Doe, email : email@example.com');
|
expect(result).toEqual('John Doe, email : email@example.com');
|
||||||
@ -540,17 +557,19 @@ describe('BlockService', () => {
|
|||||||
|
|
||||||
it('should process text replacements with context vars', () => {
|
it('should process text replacements with context vars', () => {
|
||||||
const result = blockService.processText(
|
const result = blockService.processText(
|
||||||
'{context.user.first_name} {context.user.last_name}, email : {context.vars.email}',
|
'{context.user.first_name} {context.user.last_name}, phone : {context.vars.phone}',
|
||||||
contextEmailVarInstance,
|
contextEmailVarInstance,
|
||||||
|
subscriberContext,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
expect(result).toEqual('John Doe, email : email@example.com');
|
expect(result).toEqual('John Doe, phone : 123456789');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should process text replacements with settings contact infos', () => {
|
it('should process text replacements with settings contact infos', () => {
|
||||||
const result = blockService.processText(
|
const result = blockService.processText(
|
||||||
'Trying the settings : the name of company is <<{contact.company_name}>>',
|
'Trying the settings : the name of company is <<{contact.company_name}>>',
|
||||||
contextBlankInstance,
|
contextBlankInstance,
|
||||||
|
subscriberContext,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
} from '../schemas/types/message';
|
} from '../schemas/types/message';
|
||||||
import { NlpPattern, Pattern, PayloadPattern } from '../schemas/types/pattern';
|
import { NlpPattern, Pattern, PayloadPattern } from '../schemas/types/pattern';
|
||||||
import { Payload, StdQuickReply } from '../schemas/types/quick-reply';
|
import { Payload, StdQuickReply } from '../schemas/types/quick-reply';
|
||||||
|
import { SubscriberContext } from '../schemas/types/subscriberContext';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
||||||
@ -300,22 +301,19 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
processTokenReplacements(
|
processTokenReplacements(
|
||||||
text: string,
|
text: string,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
subscriberContext: SubscriberContext,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
): string {
|
): string {
|
||||||
|
const vars = { ...subscriberContext.vars, ...context.vars };
|
||||||
// Replace context tokens with their values
|
// Replace context tokens with their values
|
||||||
Object.keys(context.vars || {}).forEach((key) => {
|
Object.keys(vars).forEach((key) => {
|
||||||
if (
|
if (typeof vars[key] === 'string' && vars[key].indexOf(':') !== -1) {
|
||||||
typeof context.vars[key] === 'string' &&
|
const tmp = vars[key].split(':');
|
||||||
context.vars[key].indexOf(':') !== -1
|
vars[key] = tmp[1];
|
||||||
) {
|
|
||||||
const tmp = context.vars[key].split(':');
|
|
||||||
context.vars[key] = tmp[1];
|
|
||||||
}
|
}
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
'{context.vars.' + key + '}',
|
'{context.vars.' + key + '}',
|
||||||
typeof context.vars[key] === 'string'
|
typeof vars[key] === 'string' ? vars[key] : JSON.stringify(vars[key]),
|
||||||
? context.vars[key]
|
|
||||||
: JSON.stringify(context.vars[key]),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -367,14 +365,24 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
*
|
*
|
||||||
* @returns The text message translated and tokens being replaces with values
|
* @returns The text message translated and tokens being replaces with values
|
||||||
*/
|
*/
|
||||||
processText(text: string, context: Context, settings: Settings): string {
|
processText(
|
||||||
|
text: string,
|
||||||
|
context: Context,
|
||||||
|
subscriberContext: SubscriberContext,
|
||||||
|
settings: Settings,
|
||||||
|
): string {
|
||||||
// Translate
|
// Translate
|
||||||
text = this.i18n.t(text, {
|
text = this.i18n.t(text, {
|
||||||
lang: context.user.language,
|
lang: context.user.language,
|
||||||
defaultValue: text,
|
defaultValue: text,
|
||||||
});
|
});
|
||||||
// Replace context tokens
|
// Replace context tokens
|
||||||
text = this.processTokenReplacements(text, context, settings);
|
text = this.processTokenReplacements(
|
||||||
|
text,
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,6 +429,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
async processMessage(
|
async processMessage(
|
||||||
block: Block | BlockFull,
|
block: Block | BlockFull,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
subscriberContext: SubscriberContext,
|
||||||
fallback = false,
|
fallback = false,
|
||||||
conversationId?: string,
|
conversationId?: string,
|
||||||
): Promise<StdOutgoingEnvelope> {
|
): Promise<StdOutgoingEnvelope> {
|
||||||
@ -438,6 +447,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
const text = this.processText(
|
const text = this.processText(
|
||||||
this.getRandom(blockMessage),
|
this.getRandom(blockMessage),
|
||||||
context,
|
context,
|
||||||
|
subscriberContext,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
const envelope: StdOutgoingEnvelope = {
|
const envelope: StdOutgoingEnvelope = {
|
||||||
@ -454,12 +464,22 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
const envelope: StdOutgoingEnvelope = {
|
const envelope: StdOutgoingEnvelope = {
|
||||||
format: OutgoingMessageFormat.quickReplies,
|
format: OutgoingMessageFormat.quickReplies,
|
||||||
message: {
|
message: {
|
||||||
text: this.processText(blockMessage.text, context, settings),
|
text: this.processText(
|
||||||
|
blockMessage.text,
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
),
|
||||||
quickReplies: blockMessage.quickReplies.map((qr: StdQuickReply) => {
|
quickReplies: blockMessage.quickReplies.map((qr: StdQuickReply) => {
|
||||||
return qr.title
|
return qr.title
|
||||||
? {
|
? {
|
||||||
...qr,
|
...qr,
|
||||||
title: this.processText(qr.title, context, settings),
|
title: this.processText(
|
||||||
|
qr.title,
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
: qr;
|
: qr;
|
||||||
}),
|
}),
|
||||||
@ -474,12 +494,22 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
|||||||
const envelope: StdOutgoingEnvelope = {
|
const envelope: StdOutgoingEnvelope = {
|
||||||
format: OutgoingMessageFormat.buttons,
|
format: OutgoingMessageFormat.buttons,
|
||||||
message: {
|
message: {
|
||||||
text: this.processText(blockMessage.text, context, settings),
|
text: this.processText(
|
||||||
|
blockMessage.text,
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
),
|
||||||
buttons: blockMessage.buttons.map((btn) => {
|
buttons: blockMessage.buttons.map((btn) => {
|
||||||
return btn.title
|
return btn.title
|
||||||
? {
|
? {
|
||||||
...btn,
|
...btn,
|
||||||
title: this.processText(btn.title, context, settings),
|
title: this.processText(
|
||||||
|
btn.title,
|
||||||
|
context,
|
||||||
|
subscriberContext,
|
||||||
|
settings,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
: btn;
|
: btn;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -62,15 +62,18 @@ import { CategoryRepository } from './../repositories/category.repository';
|
|||||||
import { BlockService } from './block.service';
|
import { BlockService } from './block.service';
|
||||||
import { BotService } from './bot.service';
|
import { BotService } from './bot.service';
|
||||||
import { CategoryService } from './category.service';
|
import { CategoryService } from './category.service';
|
||||||
|
import { ContextVarService } from './context-var.service';
|
||||||
import { ConversationService } from './conversation.service';
|
import { ConversationService } from './conversation.service';
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
import { SubscriberService } from './subscriber.service';
|
import { SubscriberService } from './subscriber.service';
|
||||||
import { BlockRepository } from '../repositories/block.repository';
|
import { BlockRepository } from '../repositories/block.repository';
|
||||||
|
import { ContextVarRepository } from '../repositories/context-var.repository';
|
||||||
import { ConversationRepository } from '../repositories/conversation.repository';
|
import { ConversationRepository } from '../repositories/conversation.repository';
|
||||||
import { MessageRepository } from '../repositories/message.repository';
|
import { MessageRepository } from '../repositories/message.repository';
|
||||||
import { SubscriberRepository } from '../repositories/subscriber.repository';
|
import { SubscriberRepository } from '../repositories/subscriber.repository';
|
||||||
import { BlockFull, BlockModel } from '../schemas/block.schema';
|
import { BlockFull, BlockModel } from '../schemas/block.schema';
|
||||||
import { CategoryModel } from '../schemas/category.schema';
|
import { CategoryModel } from '../schemas/category.schema';
|
||||||
|
import { ContextVarModel } from '../schemas/context-var.schema';
|
||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationFull,
|
ConversationFull,
|
||||||
@ -110,6 +113,7 @@ describe('BlockService', () => {
|
|||||||
NlpEntityModel,
|
NlpEntityModel,
|
||||||
NlpSampleEntityModel,
|
NlpSampleEntityModel,
|
||||||
NlpSampleModel,
|
NlpSampleModel,
|
||||||
|
ContextVarModel,
|
||||||
LanguageModel,
|
LanguageModel,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
@ -148,6 +152,8 @@ describe('BlockService', () => {
|
|||||||
NlpSampleEntityService,
|
NlpSampleEntityService,
|
||||||
NlpSampleService,
|
NlpSampleService,
|
||||||
NlpService,
|
NlpService,
|
||||||
|
ContextVarService,
|
||||||
|
ContextVarRepository,
|
||||||
LanguageService,
|
LanguageService,
|
||||||
{
|
{
|
||||||
provide: PluginService,
|
provide: PluginService,
|
||||||
|
|||||||
@ -70,10 +70,12 @@ export class BotService {
|
|||||||
event.getSenderForeignId(),
|
event.getSenderForeignId(),
|
||||||
);
|
);
|
||||||
// Process message : Replace tokens with context data and then send the message
|
// Process message : Replace tokens with context data and then send the message
|
||||||
|
const recipient = event.getSender();
|
||||||
const envelope: StdOutgoingEnvelope =
|
const envelope: StdOutgoingEnvelope =
|
||||||
await this.blockService.processMessage(
|
await this.blockService.processMessage(
|
||||||
block,
|
block,
|
||||||
context,
|
context,
|
||||||
|
recipient.context,
|
||||||
fallback,
|
fallback,
|
||||||
conservationId,
|
conservationId,
|
||||||
);
|
);
|
||||||
@ -87,7 +89,6 @@ export class BotService {
|
|||||||
this.eventEmitter.emit('hook:stats:entry', 'all_messages', 'All Messages');
|
this.eventEmitter.emit('hook:stats:entry', 'all_messages', 'All Messages');
|
||||||
|
|
||||||
// Trigger sent message event
|
// Trigger sent message event
|
||||||
const recipient = event.getSender();
|
|
||||||
const sentMessage: MessageCreateDto = {
|
const sentMessage: MessageCreateDto = {
|
||||||
mid: response && 'mid' in response ? response.mid : '',
|
mid: response && 'mid' in response ? response.mid : '',
|
||||||
message: envelope.message,
|
message: envelope.message,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { BaseService } from '@/utils/generics/base-service';
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
|
|
||||||
import { ContextVarRepository } from '../repositories/context-var.repository';
|
import { ContextVarRepository } from '../repositories/context-var.repository';
|
||||||
|
import { Block, BlockFull } from '../schemas/block.schema';
|
||||||
import { ContextVar } from '../schemas/context-var.schema';
|
import { ContextVar } from '../schemas/context-var.schema';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -19,4 +20,22 @@ export class ContextVarService extends BaseService<ContextVar> {
|
|||||||
constructor(readonly repository: ContextVarRepository) {
|
constructor(readonly repository: ContextVarRepository) {
|
||||||
super(repository);
|
super(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a mapping of context variable names to their corresponding `ContextVar` objects for a given block.
|
||||||
|
*
|
||||||
|
* @param {Block | BlockFull} block - The block containing the capture variables to retrieve context variables for.
|
||||||
|
* @returns {Promise<Record<string, ContextVar>>} A promise that resolves to a record mapping context variable names to `ContextVar` objects.
|
||||||
|
*/
|
||||||
|
async getContextVarsByBlock(
|
||||||
|
block: Block | BlockFull,
|
||||||
|
): Promise<Record<string, ContextVar>> {
|
||||||
|
const vars = await this.find({
|
||||||
|
name: { $in: block.capture_vars.map(({ context_var }) => context_var) },
|
||||||
|
});
|
||||||
|
return vars.reduce((acc, cv) => {
|
||||||
|
acc[cv.name] = cv;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,13 +7,15 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { FilterQuery } from 'mongoose';
|
import { FilterQuery } from 'mongoose';
|
||||||
|
|
||||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { BaseService } from '@/utils/generics/base-service';
|
import { BaseService } from '@/utils/generics/base-service';
|
||||||
|
|
||||||
|
import { ContextVarService } from './context-var.service';
|
||||||
|
import { SubscriberService } from './subscriber.service';
|
||||||
import { VIEW_MORE_PAYLOAD } from '../helpers/constants';
|
import { VIEW_MORE_PAYLOAD } from '../helpers/constants';
|
||||||
import { ConversationRepository } from '../repositories/conversation.repository';
|
import { ConversationRepository } from '../repositories/conversation.repository';
|
||||||
import { Block, BlockFull } from '../schemas/block.schema';
|
import { Block, BlockFull } from '../schemas/block.schema';
|
||||||
@ -34,6 +36,8 @@ export class ConversationService extends BaseService<
|
|||||||
constructor(
|
constructor(
|
||||||
readonly repository: ConversationRepository,
|
readonly repository: ConversationRepository,
|
||||||
private readonly logger: LoggerService,
|
private readonly logger: LoggerService,
|
||||||
|
private readonly contextVarService: ContextVarService,
|
||||||
|
private readonly subscriberService: SubscriberService,
|
||||||
) {
|
) {
|
||||||
super(repository);
|
super(repository);
|
||||||
}
|
}
|
||||||
@ -79,6 +83,7 @@ export class ConversationService extends BaseService<
|
|||||||
captureVars: boolean = false,
|
captureVars: boolean = false,
|
||||||
) {
|
) {
|
||||||
const msgType = event.getMessageType();
|
const msgType = event.getMessageType();
|
||||||
|
const profile = event.getSender();
|
||||||
// Capture channel specific context data
|
// Capture channel specific context data
|
||||||
convo.context.channel = event.getHandler().getChannel();
|
convo.context.channel = event.getHandler().getChannel();
|
||||||
convo.context.text = event.getText();
|
convo.context.text = event.getText();
|
||||||
@ -86,6 +91,9 @@ export class ConversationService extends BaseService<
|
|||||||
convo.context.nlp = event.getNLP();
|
convo.context.nlp = event.getNLP();
|
||||||
convo.context.vars = convo.context.vars || {};
|
convo.context.vars = convo.context.vars || {};
|
||||||
|
|
||||||
|
const contextVars =
|
||||||
|
await this.contextVarService.getContextVarsByBlock(next);
|
||||||
|
|
||||||
// Capture user entry in context vars
|
// Capture user entry in context vars
|
||||||
if (captureVars && next.capture_vars && next.capture_vars.length > 0) {
|
if (captureVars && next.capture_vars && next.capture_vars.length > 0) {
|
||||||
next.capture_vars.forEach((capture) => {
|
next.capture_vars.forEach((capture) => {
|
||||||
@ -121,12 +129,18 @@ export class ConversationService extends BaseService<
|
|||||||
contextValue =
|
contextValue =
|
||||||
typeof contextValue === 'string' ? contextValue.trim() : contextValue;
|
typeof contextValue === 'string' ? contextValue.trim() : contextValue;
|
||||||
|
|
||||||
convo.context.vars[capture.context_var] = contextValue;
|
if (contextVars[capture.context_var]?.permanent) {
|
||||||
|
Logger.debug(
|
||||||
|
`Adding context var to subscriber: ${capture.context_var} = ${contextValue}`,
|
||||||
|
);
|
||||||
|
profile.context.vars[capture.context_var] = contextValue;
|
||||||
|
} else {
|
||||||
|
convo.context.vars[capture.context_var] = contextValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store user infos
|
// Store user infos
|
||||||
const profile = event.getSender();
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
// @ts-expect-error : id needs to remain readonly
|
// @ts-expect-error : id needs to remain readonly
|
||||||
convo.context.user.id = profile.id;
|
convo.context.user.id = profile.id;
|
||||||
@ -182,6 +196,13 @@ export class ConversationService extends BaseService<
|
|||||||
'Conversation Model : No conversation has been updated',
|
'Conversation Model : No conversation has been updated',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: add check if nothing changed don't update
|
||||||
|
|
||||||
|
await this.subscriberService.updateOne(convo.sender, {
|
||||||
|
context: profile.context,
|
||||||
|
});
|
||||||
|
|
||||||
return updatedConversation;
|
return updatedConversation;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error('Conversation Model : Unable to store context', err);
|
this.logger.error('Conversation Model : Unable to store context', err);
|
||||||
|
|||||||
2
api/src/utils/test/fixtures/contextvar.ts
vendored
2
api/src/utils/test/fixtures/contextvar.ts
vendored
@ -18,10 +18,12 @@ const contextVars: ContextVarCreateDto[] = [
|
|||||||
{
|
{
|
||||||
label: 'test context var 1',
|
label: 'test context var 1',
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
|
permanent: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'test context var 2',
|
label: 'test context var 2',
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
|
permanent: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
import { Block, BlockStub } from '@/chat/schemas/block.schema';
|
import { Block, BlockStub } from '@/chat/schemas/block.schema';
|
||||||
import { ConversationFull } from '@/chat/schemas/conversation.schema';
|
import { ConversationFull } from '@/chat/schemas/conversation.schema';
|
||||||
import { Context } from '@/chat/schemas/types/context';
|
import { Context } from '@/chat/schemas/types/context';
|
||||||
|
import { SubscriberContext } from '@/chat/schemas/types/subscriberContext';
|
||||||
|
|
||||||
import { quickRepliesBlock, textBlock } from './block';
|
import { quickRepliesBlock, textBlock } from './block';
|
||||||
import { modelInstance } from './misc';
|
import { modelInstance } from './misc';
|
||||||
@ -30,6 +31,10 @@ export const contextBlankInstance: Context = {
|
|||||||
attempt: 1,
|
attempt: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const subscriberContextBlankInstance: SubscriberContext = {
|
||||||
|
vars: {},
|
||||||
|
};
|
||||||
|
|
||||||
export const contextEmailVarInstance: Context = {
|
export const contextEmailVarInstance: Context = {
|
||||||
...contextBlankInstance,
|
...contextBlankInstance,
|
||||||
vars: {
|
vars: {
|
||||||
|
|||||||
@ -7,9 +7,16 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Dialog, DialogActions, DialogContent } from "@mui/material";
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
FormControlLabel,
|
||||||
|
FormHelperText,
|
||||||
|
Switch,
|
||||||
|
} from "@mui/material";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import DialogButtons from "@/app-components/buttons/DialogButtons";
|
import DialogButtons from "@/app-components/buttons/DialogButtons";
|
||||||
@ -58,6 +65,7 @@ export const ContextVarDialog: FC<ContextVarDialogProps> = ({
|
|||||||
setValue,
|
setValue,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
|
control,
|
||||||
} = useForm<IContextVarAttributes>({
|
} = useForm<IContextVarAttributes>({
|
||||||
defaultValues: { name: data?.name || "", label: data?.label || "" },
|
defaultValues: { name: data?.name || "", label: data?.label || "" },
|
||||||
});
|
});
|
||||||
@ -129,6 +137,19 @@ export const ContextVarDialog: FC<ContextVarDialogProps> = ({
|
|||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
/>
|
/>
|
||||||
</ContentItem>
|
</ContentItem>
|
||||||
|
<ContentItem>
|
||||||
|
<Controller
|
||||||
|
name="permanent"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch {...field} checked={field.value} />}
|
||||||
|
label={t("label.permanent")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormHelperText>{t("help.permanent")}</FormHelperText>
|
||||||
|
</ContentItem>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import { faAsterisk } from "@fortawesome/free-solid-svg-icons";
|
import { faAsterisk } from "@fortawesome/free-solid-svg-icons";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import { Button, Grid, Paper } from "@mui/material";
|
import { Button, Grid, Paper, Switch } from "@mui/material";
|
||||||
import { GridColDef } from "@mui/x-data-grid";
|
import { GridColDef } from "@mui/x-data-grid";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -24,6 +24,7 @@ import { renderHeader } from "@/app-components/tables/columns/renderHeader";
|
|||||||
import { DataGrid } from "@/app-components/tables/DataGrid";
|
import { DataGrid } from "@/app-components/tables/DataGrid";
|
||||||
import { useDelete } from "@/hooks/crud/useDelete";
|
import { useDelete } from "@/hooks/crud/useDelete";
|
||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||||
import { useHasPermission } from "@/hooks/useHasPermission";
|
import { useHasPermission } from "@/hooks/useHasPermission";
|
||||||
import { useSearch } from "@/hooks/useSearch";
|
import { useSearch } from "@/hooks/useSearch";
|
||||||
@ -52,6 +53,14 @@ export const ContextVars = () => {
|
|||||||
params: searchPayload,
|
params: searchPayload,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const { mutateAsync: updateContextVar } = useUpdate(EntityType.CONTEXT_VAR, {
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t("message.internal_server_error"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
toast.success(t("message.success_save"));
|
||||||
|
},
|
||||||
|
});
|
||||||
const { mutateAsync: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, {
|
const { mutateAsync: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, {
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("message.internal_server_error"));
|
toast.error(t("message.internal_server_error"));
|
||||||
@ -87,6 +96,27 @@ export const ContextVars = () => {
|
|||||||
renderHeader,
|
renderHeader,
|
||||||
headerAlign: "left",
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
maxWidth: 120,
|
||||||
|
field: "permanent",
|
||||||
|
headerName: t("label.permanent"),
|
||||||
|
disableColumnMenu: true,
|
||||||
|
renderHeader,
|
||||||
|
headerAlign: "left",
|
||||||
|
renderCell: (params) => (
|
||||||
|
<Switch
|
||||||
|
checked={params.value}
|
||||||
|
color="primary"
|
||||||
|
inputProps={{ "aria-label": "primary checkbox" }}
|
||||||
|
onChange={() => {
|
||||||
|
updateContextVar({
|
||||||
|
id: params.row.id,
|
||||||
|
params: { permanent: !params.value },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
maxWidth: 140,
|
maxWidth: 140,
|
||||||
field: "createdAt",
|
field: "createdAt",
|
||||||
|
|||||||
@ -292,6 +292,7 @@
|
|||||||
"assign_labels": "Assign labels",
|
"assign_labels": "Assign labels",
|
||||||
"replacement_tokens": "Replacement Tokens",
|
"replacement_tokens": "Replacement Tokens",
|
||||||
"built_in": "Built-in",
|
"built_in": "Built-in",
|
||||||
|
"permanent": "Permanent",
|
||||||
"assign_to": "Takeover By",
|
"assign_to": "Takeover By",
|
||||||
"assigned_to": "Assigned To",
|
"assigned_to": "Assigned To",
|
||||||
"user_first_name": "First Name",
|
"user_first_name": "First Name",
|
||||||
@ -698,7 +699,8 @@
|
|||||||
"supported_message_type_others": "Only generic template messages are supported.",
|
"supported_message_type_others": "Only generic template messages are supported.",
|
||||||
"notification_type_regular": "Sound/Vibration",
|
"notification_type_regular": "Sound/Vibration",
|
||||||
"notification_type_silent_push": "On-screen notification only",
|
"notification_type_silent_push": "On-screen notification only",
|
||||||
"notification_type_no_push": "No notification"
|
"notification_type_no_push": "No notification",
|
||||||
|
"permanent": "When enabled, the variable value will be stored in the subscriber's profile and retained for future conversations."
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"messages": "Messages",
|
"messages": "Messages",
|
||||||
|
|||||||
@ -293,6 +293,7 @@
|
|||||||
"assign_labels": "Affecter des étiquettes",
|
"assign_labels": "Affecter des étiquettes",
|
||||||
"replacement_tokens": "Jetons de remplacement",
|
"replacement_tokens": "Jetons de remplacement",
|
||||||
"built_in": "Intégré",
|
"built_in": "Intégré",
|
||||||
|
"permanent": "Permanent",
|
||||||
"assign_to": "Assigner à",
|
"assign_to": "Assigner à",
|
||||||
"assigned_to": "Assigné(e) à",
|
"assigned_to": "Assigné(e) à",
|
||||||
"user_first_name": "Prénom",
|
"user_first_name": "Prénom",
|
||||||
@ -696,7 +697,8 @@
|
|||||||
"supported_message_type_others": "Seuls les messages de modèle générique sont pris en charge.",
|
"supported_message_type_others": "Seuls les messages de modèle générique sont pris en charge.",
|
||||||
"notification_type_regular": "Son/Vibration",
|
"notification_type_regular": "Son/Vibration",
|
||||||
"notification_type_silent_push": "Notification à l'écran uniquement",
|
"notification_type_silent_push": "Notification à l'écran uniquement",
|
||||||
"notification_type_no_push": "Aucune notification"
|
"notification_type_no_push": "Aucune notification",
|
||||||
|
"permanent": "Lorsqu'elle est activée, cette variable sera stockée dans le profil de l'abonné(e) et conservée pour les futures conversations."
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"messages": "Messages",
|
"messages": "Messages",
|
||||||
|
|||||||
@ -14,14 +14,12 @@ import { IBaseSchema, IFormat, OmitPopulate } from "./base.types";
|
|||||||
export interface IContextVarAttributes {
|
export interface IContextVarAttributes {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
permanent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IContextVarStub
|
export interface IContextVarStub
|
||||||
extends IBaseSchema,
|
extends IBaseSchema,
|
||||||
OmitPopulate<IContextVarAttributes, EntityType.CONTEXT_VAR> {
|
OmitPopulate<IContextVarAttributes, EntityType.CONTEXT_VAR> {}
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IContextVar extends IContextVarStub, IFormat<Format.BASIC> {}
|
export interface IContextVar extends IContextVarStub, IFormat<Format.BASIC> {}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user