Merge pull request #243 from Hexastack/feat/api-i18n

feat: fetch remote i18n (partial - for channels only)
This commit is contained in:
Med Marrouchi 2024-10-21 08:31:09 +01:00 committed by GitHub
commit 95f45b75ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1890 additions and 1705 deletions

View File

@ -72,4 +72,4 @@ module.exports = {
},
],
},
};
};

22
api/package-lock.json generated
View File

@ -51,6 +51,7 @@
"patch-package": "^8.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"slug": "^8.2.2",
"ts-migrate-mongoose": "^3.8.4",
"uuid": "^9.0.1"
@ -16980,6 +16981,14 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sanitize-filename": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
"dependencies": {
"truncate-utf8-bytes": "^1.0.0"
}
},
"node_modules/sax": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
@ -18114,6 +18123,14 @@
"tree-kill": "cli.js"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
"integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
"dependencies": {
"utf8-byte-length": "^1.0.1"
}
},
"node_modules/ts-api-utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
@ -18657,6 +18674,11 @@
"punycode": "^2.1.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -72,6 +72,7 @@
"patch-package": "^8.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"slug": "^8.2.2",
"ts-migrate-mongoose": "^3.8.4",
"uuid": "^9.0.1"

View File

@ -26,7 +26,7 @@ import ChannelHandler from './lib/Handler';
@Injectable()
export class ChannelService {
private registry: Map<string, ChannelHandler> = new Map();
private registry: Map<string, ChannelHandler<string>> = new Map();
constructor(
private readonly logger: LoggerService,
@ -40,7 +40,10 @@ export class ChannelService {
* @param channel - The channel handler associated with the channel name.
* @typeParam C The channel handler's type that extends `ChannelHandler`.
*/
public setChannel<C extends ChannelHandler>(name: string, channel: C) {
public setChannel<T extends string, C extends ChannelHandler<T>>(
name: T,
channel: C,
) {
this.registry.set(name, channel);
}
@ -71,7 +74,9 @@ export class ChannelService {
* @param channelName - The name of the channel (messenger, offline, ...).
* @returns The handler for the specified channel.
*/
public getChannelHandler<C extends ChannelHandler>(name: string): C {
public getChannelHandler<T extends string, C extends ChannelHandler<T>>(
name: T,
): C {
const handler = this.registry.get(name);
if (!handler) {
throw new Error(`Channel ${name} not found`);
@ -98,8 +103,8 @@ export class ChannelService {
* @param req - The websocket request object.
* @param res - The websocket response object.
*/
@SocketGet('/webhook/offline/')
@SocketPost('/webhook/offline/')
@SocketGet(`/webhook/${OFFLINE_CHANNEL_NAME}/`)
@SocketPost(`/webhook/${OFFLINE_CHANNEL_NAME}/`)
handleWebsocketForOffline(
@SocketReq() req: SocketRequest,
@SocketRes() res: SocketResponse,
@ -116,8 +121,8 @@ export class ChannelService {
* @param req - The websocket request object.
* @param res - The websocket response object.
*/
@SocketGet('/webhook/live-chat-tester/')
@SocketPost('/webhook/live-chat-tester/')
@SocketGet(`/webhook/${LIVE_CHAT_TEST_CHANNEL_NAME}/`)
@SocketPost(`/webhook/${LIVE_CHAT_TEST_CHANNEL_NAME}/`)
async handleWebsocketForLiveChatTester(
@SocketReq() req: SocketRequest,
@SocketRes() res: SocketResponse,

View File

@ -18,35 +18,55 @@ import {
import { LoggerService } from '@/logger/logger.service';
import BaseNlpHelper from '@/nlp/lib/BaseNlpHelper';
import { NlpService } from '@/nlp/services/nlp.service';
import { SettingCreateDto } from '@/setting/dto/setting.dto';
import { SettingService } from '@/setting/services/setting.service';
import { SocketRequest } from '@/websocket/utils/socket-request';
import { SocketResponse } from '@/websocket/utils/socket-response';
import { ChannelService } from '../channel.service';
import { ChannelSetting } from '../types';
import EventWrapper from './EventWrapper';
@Injectable()
export default abstract class ChannelHandler {
protected settings: SettingCreateDto[] = [];
export default abstract class ChannelHandler<N extends string = string> {
private readonly name: N;
private readonly settings: ChannelSetting<N>[];
protected NLP: BaseNlpHelper;
constructor(
name: N,
settings: ChannelSetting<N>[],
protected readonly settingService: SettingService,
private readonly channelService: ChannelService,
protected readonly nlpService: NlpService,
protected readonly logger: LoggerService,
) {}
) {
this.name = name;
this.settings = settings;
}
onModuleInit() {
this.channelService.setChannel(this.getChannel(), this);
this.channelService.setChannel(
this.getChannel(),
this as unknown as ChannelHandler<N>,
);
this.setup();
}
protected getGroup() {
return this.getChannel().replaceAll('-', '_') as ChannelSetting<N>['group'];
}
async setup() {
await this.settingService.seedIfNotExist(this.getChannel(), this.settings);
await this.settingService.seedIfNotExist(
this.getChannel(),
this.settings.map((s, i) => ({
...s,
weight: i + 1,
})),
);
const nlp = this.nlpService.getNLP();
this.setNLP(nlp);
this.init();
@ -61,30 +81,33 @@ export default abstract class ChannelHandler {
}
/**
* Returns the channel specific settings
* Returns the channel's name
* @returns Channel's name
*/
async getSettings<S>() {
const settings = await this.settingService.getSettings();
return settings[this.getChannel()] as S;
getChannel() {
return this.name;
}
/**
* Returns the channel's name
* @returns {String}
* Returns the channel's settings
* @returns Channel's settings
*/
abstract getChannel(): string;
async getSettings<S extends string = HyphenToUnderscore<N>>() {
const settings = await this.settingService.getSettings();
// @ts-expect-error workaround typing
return settings[this.getGroup() as keyof Settings] as Settings[S];
}
/**
* Perform any initialization needed
* @returns
*/
abstract init(): void;
/**
* Process incoming channel data via POST/GET methods
*
* @param {module:Controller.req} req
* @param {module:Controller.res} res
* Process incoming channel data via POST/GET methods
*/
abstract handle(
req: Request | SocketRequest,
@ -93,26 +116,28 @@ export default abstract class ChannelHandler {
/**
* Format a text message that will be sent to the channel
*
* @param message - A text to be sent to the end user
* @param options - might contain additional settings
* @returns {Object} - A text message in the channel specific format
*/
abstract _textFormat(message: StdOutgoingMessage, options?: any): any;
/**
* Format a text + quick replies message that can be sent to the channel
*
* @param message - A text + quick replies to be sent to the end user
* @param options - might contain additional settings
* @returns {Object} - A quick replies message in the channel specific format
* Format a text + quick replies message that can be sent to the channel
*/
abstract _quickRepliesFormat(message: StdOutgoingMessage, options?: any): any;
/**
* From raw buttons, construct a channel understable message containing those buttons
*
* @param message - A text + buttons to be sent to the end user
* @param options - Might contain additional settings
* @returns {Object} - A buttons message in the format required by the channel
* From raw buttons, construct a channel understable message containing those buttons
*/
abstract _buttonsFormat(
message: StdOutgoingMessage,
@ -121,27 +146,29 @@ export default abstract class ChannelHandler {
): any;
/**
* Format an attachment + quick replies message that can be sent to the channel
*
* @param message - An attachment + quick replies to be sent to the end user
* @param options - Might contain additional settings
* @returns {Object} - An attachment message in the format required by the channel
* Format an attachment + quick replies message that can be sent to the channel
*/
abstract _attachmentFormat(message: StdOutgoingMessage, options?: any): any;
/**
* Format a collection of items to be sent to the channel in carousel/list format
*
* @param data - A list of data items to be sent to the end user
* @param options - Might contain additional settings
* @returns {Object[]} - An array of element objects
* Format a collection of items to be sent to the channel in carousel/list format
*/
abstract _formatElements(data: any[], options: any, ...args: any): any[];
/**
* Format a list of elements
*
* @param message - Contains elements to be sent to the end user
* @param options - Might contain additional settings
* @returns {Object} - A ready to be sent list template message in the format required by the channel
*/
abstract _listFormat(
message: StdOutgoingMessage,

8
api/src/channel/types.ts Normal file
View File

@ -0,0 +1,8 @@
import { SettingCreateDto } from '@/setting/dto/setting.dto';
export type ChannelSetting<N extends string = string> = Omit<
SettingCreateDto,
'group' | 'weight'
> & {
group: HyphenToUnderscore<N>;
};

View File

@ -34,7 +34,6 @@ import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { PluginService } from '@/plugins/plugins.service';
import { Settings } from '@/setting/schemas/types';
import { SettingService } from '@/setting/services/setting.service';
import {
blockFixtures,

View File

@ -18,7 +18,6 @@ import { LoggerService } from '@/logger/logger.service';
import { Nlp } from '@/nlp/lib/types';
import { PluginService } from '@/plugins/plugins.service';
import { PluginType } from '@/plugins/types';
import { Settings } from '@/setting/schemas/types';
import { SettingService } from '@/setting/services/setting.service';
import { BaseService } from '@/utils/generics/base-service';
import { getRandom } from '@/utils/helpers/safeRandom';

View File

@ -11,7 +11,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import EventWrapper from '@/channel/lib/EventWrapper';
import { LoggerService } from '@/logger/logger.service';
import { Settings } from '@/setting/schemas/types';
import { SettingService } from '@/setting/services/setting.service';
import { MessageCreateDto } from '../dto/message.dto';

View File

@ -28,7 +28,6 @@ import type {
NlpValueDocument,
} from '@/nlp/schemas/nlp-value.schema';
import { type Setting } from '@/setting/schemas/setting.schema';
import type { CheckboxSetting, TextSetting } from '@/setting/schemas/types';
import { type Invitation } from '@/user/schemas/invitation.schema';
import { type Model } from '@/user/schemas/model.schema';
import { type Permission } from '@/user/schemas/permission.schema';
@ -48,17 +47,7 @@ declare module '@nestjs/event-emitter' {
operations: O;
}
interface IHookExtensionsOperationMap {
messenger: TDefinition<
object,
{
get_started_button: Setting;
access_token: Setting;
composer_input_disabled: CheckboxSetting;
greeting_text: TextSetting;
}
>;
}
interface IHookExtensionsOperationMap {}
interface IHookSettingsGroupLabelOperationMap {
chatbot_settings: TDefinition<

View File

@ -0,0 +1,23 @@
{
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}

View File

@ -0,0 +1,3 @@
{
"live_chat_tester": "Live Chat Tester"
}

View File

@ -0,0 +1,23 @@
{
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}

View File

@ -0,0 +1,3 @@
{
"live_chat_tester": "Testeur Live Chat"
}

View File

@ -17,11 +17,10 @@ import { MenuService } from '@/cms/services/menu.service';
import { I18nService } from '@/i18n/services/i18n.service';
import { LoggerService } from '@/logger/logger.service';
import { NlpService } from '@/nlp/services/nlp.service';
import { SettingCreateDto } from '@/setting/dto/setting.dto';
import { SettingService } from '@/setting/services/setting.service';
import { WebsocketGateway } from '@/websocket/websocket.gateway';
import OfflineHandler from '../offline/index.channel';
import BaseWebChannelHandler from '../offline/base-web-channel';
import {
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
@ -29,9 +28,9 @@ import {
} from './settings';
@Injectable()
export default class LiveChatTesterHandler extends OfflineHandler {
protected settings: SettingCreateDto[] = DEFAULT_LIVE_CHAT_TEST_SETTINGS;
export default class LiveChatTesterHandler extends BaseWebChannelHandler<
typeof LIVE_CHAT_TEST_CHANNEL_NAME
> {
constructor(
settingService: SettingService,
channelService: ChannelService,
@ -46,6 +45,8 @@ export default class LiveChatTesterHandler extends OfflineHandler {
websocketGateway: WebsocketGateway,
) {
super(
LIVE_CHAT_TEST_CHANNEL_NAME,
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
settingService,
channelService,
nlpService,
@ -59,12 +60,4 @@ export default class LiveChatTesterHandler extends OfflineHandler {
websocketGateway,
);
}
/**
* Returns the channel's name
* @returns {String}
*/
getChannel() {
return LIVE_CHAT_TEST_CHANNEL_NAME;
}
}

View File

@ -0,0 +1,18 @@
import {
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
LIVE_CHAT_TEST_GROUP_NAME,
} from './settings';
declare global {
interface Settings
extends SettingTree<typeof DEFAULT_LIVE_CHAT_TEST_SETTINGS> {}
}
declare module '@nestjs/event-emitter' {
interface IHookExtensionsOperationMap {
[LIVE_CHAT_TEST_GROUP_NAME]: TDefinition<
object,
SettingMapByType<typeof DEFAULT_LIVE_CHAT_TEST_SETTINGS>
>;
}
}

View File

@ -6,99 +6,89 @@
* 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 { ChannelSetting } from '@/channel/types';
import { config } from '@/config';
import { SettingCreateDto } from '@/setting/dto/setting.dto';
import { SettingType } from '@/setting/schemas/types';
import { Offline } from '../offline/types';
export const LIVE_CHAT_TEST_CHANNEL_NAME = 'live-chat-tester';
export const DEFAULT_LIVE_CHAT_TEST_SETTINGS: SettingCreateDto[] = [
export const LIVE_CHAT_TEST_GROUP_NAME = 'live_chat_tester';
export const DEFAULT_LIVE_CHAT_TEST_SETTINGS = [
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.verification_token,
value: 'test',
type: SettingType.text,
weight: 2,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.allowed_domains,
value: config.frontendPath,
type: SettingType.text,
weight: 3,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.start_button,
value: true,
type: SettingType.checkbox,
weight: 4,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.input_disabled,
value: false,
type: SettingType.checkbox,
weight: 5,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.persistent_menu,
value: true,
type: SettingType.checkbox,
weight: 6,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.greeting_message,
value: 'Welcome! Ready to start a conversation with our chatbot?',
type: SettingType.textarea,
weight: 7,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.theme_color,
value: 'teal',
type: SettingType.select,
options: ['teal', 'orange', 'red', 'green', 'blue', 'dark'],
weight: 8,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.show_emoji,
value: true,
type: SettingType.checkbox,
weight: 11,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.show_file,
value: true,
type: SettingType.checkbox,
weight: 12,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.show_location,
value: true,
type: SettingType.checkbox,
weight: 13,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.allowed_upload_size,
value: 2500000,
type: SettingType.number,
weight: 14,
},
{
group: LIVE_CHAT_TEST_CHANNEL_NAME,
group: LIVE_CHAT_TEST_GROUP_NAME,
label: Offline.SettingLabel.allowed_upload_types,
value:
'audio/mpeg,audio/x-ms-wma,audio/vnd.rn-realaudio,audio/x-wav,image/gif,image/jpeg,image/png,image/tiff,image/vnd.microsoft.icon,image/vnd.djvu,image/svg+xml,text/css,text/csv,text/html,text/plain,text/xml,video/mpeg,video/mp4,video/quicktime,video/x-ms-wmv,video/x-msvideo,video/x-flv,video/web,application/msword,application/vnd.ms-powerpoint,application/pdf,application/vnd.ms-excel,application/vnd.oasis.opendocument.presentation,application/vnd.oasis.opendocument.tex,application/vnd.oasis.opendocument.spreadsheet,application/vnd.oasis.opendocument.graphics,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
type: SettingType.textarea,
weight: 15,
},
];
] as const satisfies ChannelSetting<typeof LIVE_CHAT_TEST_CHANNEL_NAME>[];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}

View File

@ -0,0 +1,3 @@
{
"offline": "Canal Web"
}

View File

@ -0,0 +1,23 @@
{
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}

View File

@ -0,0 +1,3 @@
{
"live_chat_tester": "Testeur Live Chat"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
import { DEFAULT_OFFLINE_SETTINGS, OFFLINE_GROUP_NAME } from './settings';
declare global {
interface Settings extends SettingTree<typeof DEFAULT_OFFLINE_SETTINGS> {}
}
declare module '@nestjs/event-emitter' {
interface IHookExtensionsOperationMap {
[OFFLINE_GROUP_NAME]: TDefinition<
object,
SettingMapByType<typeof DEFAULT_OFFLINE_SETTINGS>
>;
}
}

View File

@ -6,112 +6,100 @@
* 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 { SettingCreateDto } from '@/setting/dto/setting.dto';
import { ChannelSetting } from '@/channel/types';
import { SettingType } from '@/setting/schemas/types';
import { Offline } from './types';
export const OFFLINE_CHANNEL_NAME = 'offline';
export const OFFLINE_CHANNEL_NAME = 'offline' as const;
export const DEFAULT_OFFLINE_SETTINGS: SettingCreateDto[] = [
export const OFFLINE_GROUP_NAME = OFFLINE_CHANNEL_NAME;
export const DEFAULT_OFFLINE_SETTINGS = [
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.verification_token,
value: 'token123',
type: SettingType.secret,
weight: 2,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.allowed_domains,
value: 'http://localhost:8080,http://localhost:4000',
type: SettingType.text,
weight: 3,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.start_button,
value: true,
type: SettingType.checkbox,
weight: 4,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.input_disabled,
value: false,
type: SettingType.checkbox,
weight: 5,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.persistent_menu,
value: true,
type: SettingType.checkbox,
weight: 6,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.greeting_message,
value: 'Welcome! Ready to start a conversation with our chatbot?',
type: SettingType.textarea,
weight: 7,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.theme_color,
value: 'teal',
type: SettingType.select,
options: ['teal', 'orange', 'red', 'green', 'blue', 'dark'],
weight: 8,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.window_title,
value: 'Widget Title',
type: SettingType.text,
weight: 9,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.avatar_url,
value: 'https://eu.ui-avatars.com/api/?name=Hexa+Bot&size=64',
type: SettingType.text,
weight: 10,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.show_emoji,
value: true,
type: SettingType.checkbox,
weight: 11,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.show_file,
value: true,
type: SettingType.checkbox,
weight: 12,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.show_location,
value: true,
type: SettingType.checkbox,
weight: 13,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.allowed_upload_size,
value: 2500000,
type: SettingType.number,
weight: 14,
},
{
group: OFFLINE_CHANNEL_NAME,
group: OFFLINE_GROUP_NAME,
label: Offline.SettingLabel.allowed_upload_types,
value:
'audio/mpeg,audio/x-ms-wma,audio/vnd.rn-realaudio,audio/x-wav,image/gif,image/jpeg,image/png,image/tiff,image/vnd.microsoft.icon,image/vnd.djvu,image/svg+xml,text/css,text/csv,text/html,text/plain,text/xml,video/mpeg,video/mp4,video/quicktime,video/x-ms-wmv,video/x-msvideo,video/x-flv,video/web,application/msword,application/vnd.ms-powerpoint,application/pdf,application/vnd.ms-excel,application/vnd.oasis.opendocument.presentation,application/vnd.oasis.opendocument.tex,application/vnd.oasis.opendocument.spreadsheet,application/vnd.oasis.opendocument.graphics,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
type: SettingType.textarea,
weight: 15,
},
];
] as const satisfies ChannelSetting<typeof OFFLINE_CHANNEL_NAME>[];

View File

@ -19,7 +19,7 @@ import {
} from '@/chat/schemas/types/message';
import { Payload } from '@/chat/schemas/types/quick-reply';
import OfflineHandler from './index.channel';
import BaseWebChannelHandler from './base-web-channel';
import { Offline } from './types';
type OfflineEventAdapter =
@ -66,10 +66,9 @@ type OfflineEventAdapter =
raw: Offline.IncomingMessage<Offline.IncomingAttachmentMessage>;
};
export default class OfflineEventWrapper extends EventWrapper<
OfflineEventAdapter,
Offline.Event
> {
export default class OfflineEventWrapper<
T extends BaseWebChannelHandler<string> = BaseWebChannelHandler<string>,
> extends EventWrapper<OfflineEventAdapter, Offline.Event> {
/**
* Constructor : channel's event wrapper
*
@ -77,7 +76,7 @@ export default class OfflineEventWrapper extends EventWrapper<
* @param event - The message event received
* @param channelData - Channel's specific extra data {isSocket, ipAddress}
*/
constructor(handler: OfflineHandler, event: Offline.Event, channelData: any) {
constructor(handler: T, event: Offline.Event, channelData: any) {
super(handler, event, channelData);
}

View File

@ -152,9 +152,12 @@ describe('NLP Default Helper', () => {
const nlp = nlpService.getNLP();
const results = nlp.bestGuess(nlpParseResult, true);
const settings = await settingService.getSettings();
const threshold = settings.nlp_settings.threshold;
const thresholdGuess = {
entities: nlpBestGuess.entities.filter(
(g) => g.confidence > parseFloat(settings.nlp_settings.threshold),
(g) =>
g.confidence >
(typeof threshold === 'string' ? parseFloat(threshold) : threshold),
),
};
expect(results).toEqual(thresholdGuess);

View File

@ -135,7 +135,11 @@ export default class DefaultNlpHelper extends BaseNlpHelper {
entities: nlp.entities.slice(),
};
if (threshold) {
minConfidence = Number.parseFloat(this.settings.threshold);
const threshold = this.settings.threshold;
minConfidence =
typeof threshold === 'string'
? Number.parseFloat(threshold)
: threshold;
guess.entities = guess.entities
.map((e) => {
e.confidence =

View File

@ -0,0 +1,28 @@
/*
* Copyright © 2024 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.
* 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 { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
import { I18nService } from '../services/i18n.service';
@UseInterceptors(CsrfInterceptor)
@Controller('i18n')
export class I18nController {
constructor(private readonly i18nService: I18nService) {}
/**
* Retrieves translations of all the installed extensions.
* @returns An nested object that holds the translations grouped by language and extension name.
*/
@Get()
getTranslations() {
return this.i18nService.getExtensionI18nTranslations();
}
}

View File

@ -46,10 +46,10 @@ export class LanguageController extends BaseController<Language> {
}
/**
* Retrieves a paginated list of categories based on provided filters and pagination settings.
* Retrieves a paginated list of languages based on provided filters and pagination settings.
* @param pageQuery - The pagination settings.
* @param filters - The filters to apply to the language search.
* @returns A Promise that resolves to a paginated list of categories.
* @returns A Promise that resolves to a paginated list of languages.
*/
@Get()
async findPage(
@ -61,8 +61,8 @@ export class LanguageController extends BaseController<Language> {
}
/**
* Counts the filtered number of categories.
* @returns A promise that resolves to an object representing the filtered number of categories.
* Counts the filtered number of languages.
* @returns A promise that resolves to an object representing the filtered number of languages.
*/
@Get('count')
async filterCount(

View File

@ -26,6 +26,7 @@ import { Observable } from 'rxjs';
import { ChatModule } from '@/chat/chat.module';
import { I18nController } from './controllers/i18n.controller';
import { LanguageController } from './controllers/language.controller';
import { TranslationController } from './controllers/translation.controller';
import { LanguageRepository } from './repositories/language.repository';
@ -62,6 +63,7 @@ export class I18nModule extends NativeI18nModule {
controllers: (controllers || []).concat([
LanguageController,
TranslationController,
I18nController,
]),
providers: providers.concat([
I18nService,

View File

@ -6,8 +6,13 @@
* 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 { Injectable } from '@nestjs/common';
import { promises as fs } from 'fs';
import * as path from 'path';
import { Injectable, OnModuleInit } from '@nestjs/common';
import {
I18nJsonLoader,
I18nTranslation,
I18nService as NativeI18nService,
Path,
PathValue,
@ -19,11 +24,22 @@ import { config } from '@/config';
import { Translation } from '@/i18n/schemas/translation.schema';
@Injectable()
export class I18nService<
K = Record<string, unknown>,
> extends NativeI18nService<K> {
export class I18nService<K = Record<string, unknown>>
extends NativeI18nService<K>
implements OnModuleInit
{
private dynamicTranslations: Record<string, Record<string, string>> = {};
private extensionTranslations: I18nTranslation = {};
onModuleInit() {
this.loadExtensionI18nTranslations();
}
getExtensionI18nTranslations() {
return this.extensionTranslations;
}
t<P extends Path<K> = any, R = PathValue<K, P>>(
key: P,
options?: TranslateOptions,
@ -66,4 +82,48 @@ export class I18nService<
return acc;
}, this.dynamicTranslations);
}
async loadExtensionI18nTranslations() {
const extensionsDir = path.join(
__dirname,
'..',
'..',
'extensions',
'channels',
);
try {
const extensionFolders = await fs.readdir(extensionsDir, {
withFileTypes: true,
});
for (const folder of extensionFolders) {
if (folder.isDirectory()) {
const i18nPath = path.join(extensionsDir, folder.name, 'i18n');
const extensionName = folder.name.replaceAll('-', '_');
try {
// Check if the i18n directory exists
await fs.access(i18nPath);
// Load and merge translations
const i18nLoader = new I18nJsonLoader({ path: i18nPath });
const translations = await i18nLoader.load();
for (const lang in translations) {
if (!this.extensionTranslations[lang]) {
this.extensionTranslations[lang] = {
[extensionName]: translations[lang],
};
} else {
this.extensionTranslations[lang][extensionName] =
translations[lang];
}
}
} catch (error) {
// If the i18n folder does not exist or error in reading, skip this folder
}
}
}
} catch (error) {
throw new Error(`Failed to read extensions directory: ${error.message}`);
}
}
}

View File

@ -10,7 +10,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { Test, TestingModule } from '@nestjs/testing';
import { I18nService } from '@/i18n/services/i18n.service';
import { Settings } from '@/setting/schemas/types';
import { SettingService } from '@/setting/services/setting.service';
import { Block } from '../../chat/schemas/block.schema';
@ -64,7 +63,7 @@ describe('TranslationService', () => {
global_fallback: true,
fallback_message: ['Global fallback message'],
},
} as Settings),
}),
},
},
{

10
api/src/index.d.ts vendored
View File

@ -9,9 +9,9 @@
import 'mongoose';
import { SubscriberStub } from './chat/schemas/subscriber.schema';
import {
WithoutGenericAny,
RecursivePartial,
ObjectWithNestedKeys,
RecursivePartial,
WithoutGenericAny,
} from './utils/types/filter.types';
type TOmitId<T> = Omit<T, 'id'>;
@ -63,3 +63,9 @@ declare module 'mongoose' {
type THydratedDocument<T> = TOmitId<HydratedDocument<T>>;
}
declare global {
type HyphenToUnderscore<S extends string> = S extends `${infer P}-${infer Q}`
? `${P}_${HyphenToUnderscore<Q>}`
: S;
}

View File

@ -31,7 +31,6 @@ import {
NlpValueDocument,
NlpValueFull,
} from '@/nlp/schemas/nlp-value.schema';
import { Settings } from '@/setting/schemas/types';
import { NlpEntityService } from '../services/nlp-entity.service';
import { NlpSampleService } from '../services/nlp-sample.service';

View File

@ -9,7 +9,7 @@
import { BlockCreateDto } from '@/chat/dto/block.dto';
import { Block } from '@/chat/schemas/block.schema';
import { Conversation } from '@/chat/schemas/conversation.schema';
import { Setting } from '@/setting/schemas/setting.schema';
import { SettingCreateDto } from '@/setting/dto/setting.dto';
export enum PluginType {
event = 'event',
@ -22,7 +22,7 @@ export interface CustomBlocks {}
type ChannelEvent = any;
type BlockAttrs = Partial<BlockCreateDto> & { name: string };
export type PluginSetting = Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>;
export type PluginSetting = SettingCreateDto;
export type PluginBlockTemplate = Omit<
BlockAttrs,

View File

@ -22,7 +22,7 @@ import { nlpEntityModels } from './nlp/seeds/nlp-entity.seed-model';
import { NlpValueSeeder } from './nlp/seeds/nlp-value.seed';
import { nlpValueModels } from './nlp/seeds/nlp-value.seed-model';
import { SettingSeeder } from './setting/seeds/setting.seed';
import { settingModels } from './setting/seeds/setting.seed-model';
import { DEFAULT_SETTINGS } from './setting/seeds/setting.seed-model';
import { ModelSeeder } from './user/seeds/model.seed';
import { modelModels } from './user/seeds/model.seed-model';
import { PermissionSeeder } from './user/seeds/permission.seed';
@ -106,7 +106,7 @@ export async function seedDatabase(app: INestApplicationContext) {
}
// Seed users
try {
await settingSeeder.seed(settingModels);
await settingSeeder.seed(DEFAULT_SETTINGS);
} catch (e) {
logger.error('Unable to seed the database with settings!');
throw e;

View File

@ -11,25 +11,30 @@ import {
IsArray,
IsIn,
IsNotEmpty,
IsString,
IsOptional,
IsString,
} from 'class-validator';
import { SettingType } from '../schemas/types';
export class SettingCreateDto {
@ApiProperty({ description: 'Setting group of setting', type: String })
@ApiProperty({ description: 'Setting group', type: String })
@IsNotEmpty()
@IsString()
group: string;
@ApiProperty({ description: 'Setting label of setting', type: String })
@ApiProperty({ description: 'Setting subgroup', type: String })
@IsOptional()
@IsString()
subgroup?: string;
@ApiProperty({ description: 'Setting label (system name)', type: String })
@IsNotEmpty()
@IsString()
label: string;
@ApiProperty({
description: 'Setting type of the setting',
description: 'Setting type',
enum: [
'text',
'multiple_text',
@ -44,12 +49,12 @@ export class SettingCreateDto {
@IsIn(Object.values(SettingType))
type: SettingType;
@ApiProperty({ description: 'Setting value of the setting' })
@ApiProperty({ description: 'Setting value' })
@IsNotEmpty()
value: any;
@ApiPropertyOptional({
description: 'Setting options',
description: 'Setting options (required when type is select)',
isArray: true,
type: Array,
})

38
api/src/setting/index.d.ts vendored Normal file
View File

@ -0,0 +1,38 @@
import { SettingByType } from './schemas/types';
import { DEFAULT_SETTINGS } from './seeds/setting.seed-model';
declare global {
type TNativeType<T> = T extends string
? string
: T extends number
? number
: T extends boolean
? boolean
: T extends Array<infer U>
? TNativeType<U>[]
: T extends object
? { [K in keyof T]: TNativeType<T[K]> }
: T;
type SettingObject<
T extends Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>[],
> = {
[K in T[number] as K['label']]: TNativeType<K['value']>;
};
type SettingMapByType<
T extends Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>[],
> = {
[K in T[number] as K['label']]: SettingByType<K['type']>;
};
type SettingTree<
T extends Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>[],
> = {
[G in T[number] as G['group']]: {
[K in T[number] as K['label']]: TNativeType<K['value']>;
};
};
interface Settings extends SettingTree<typeof DEFAULT_SETTINGS> {}
}

View File

@ -7,6 +7,7 @@
*/
import { ModelDefinition, Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Transform } from 'class-transformer';
import { IsArray, IsIn } from 'class-validator';
import { BaseSchema } from '@/utils/generics/base-schema';
@ -22,6 +23,13 @@ export class Setting extends BaseSchema {
})
group: string;
@Prop({
type: String,
default: '',
})
@Transform(({ obj }) => obj.subgroup || undefined)
subgroup?: string;
@Prop({
type: String,
required: true,

View File

@ -38,6 +38,20 @@ export interface TextSetting extends Setting {
config: never;
}
export interface TextareaSetting extends Setting {
type: SettingType.textarea;
value: string;
options: never;
config: never;
}
export interface SecretSetting extends Setting {
type: SettingType.secret;
value: string;
options: never;
config: never;
}
export interface MultiTextSetting extends Setting {
type: SettingType.multiple_text;
value: string[];
@ -84,6 +98,26 @@ export interface MultipleAttachmentSetting extends Setting {
config: never;
}
export type SettingByType<T extends SettingType> = T extends SettingType.text
? TextSetting
: T extends SettingType.textarea
? TextareaSetting
: T extends SettingType.secret
? SecretSetting
: T extends SettingType.multiple_text
? MultiTextSetting
: T extends SettingType.checkbox
? CheckboxSetting
: T extends SettingType.select
? SelectSetting
: T extends SettingType.number
? NumberSetting
: T extends SettingType.attachment
? AttachmentSetting
: T extends SettingType.multiple_attachment
? MultipleAttachmentSetting
: never;
export type AnySetting =
| TextSetting
| MultiTextSetting
@ -94,18 +128,3 @@ export type AnySetting =
| MultipleAttachmentSetting;
export type SettingDict = { [group: string]: Setting[] };
export type Settings = {
nlp_settings: {
threshold: string;
provider: string;
endpoint: string;
token: string;
};
contact: { [key: string]: string };
chatbot_settings: {
global_fallback: boolean;
fallback_message: string[];
fallback_block: string;
};
} & Record<string, any>;

View File

@ -9,7 +9,7 @@
import { SettingCreateDto } from '../dto/setting.dto';
import { SettingType } from '../schemas/types';
export const settingModels: SettingCreateDto[] = [
export const DEFAULT_SETTINGS = [
{
group: 'chatbot_settings',
label: 'global_fallback',
@ -38,7 +38,7 @@ export const settingModels: SettingCreateDto[] = [
value: [
"Sorry but i didn't understand your request. Maybe you can check the menu",
"I'm really sorry but i don't quite understand what you are saying :(",
],
] as string[],
type: SettingType.multiple_text,
weight: 3,
},
@ -146,4 +146,4 @@ export const settingModels: SettingCreateDto[] = [
type: SettingType.text,
weight: 10,
},
];
] as const satisfies SettingCreateDto[];

View File

@ -21,7 +21,6 @@ import { BaseService } from '@/utils/generics/base-service';
import { SettingCreateDto } from '../dto/setting.dto';
import { SettingRepository } from '../repositories/setting.repository';
import { Setting } from '../schemas/setting.schema';
import { Settings } from '../schemas/types';
import { SettingSeeder } from '../seeds/setting.seed';
@Injectable()

View File

@ -53,7 +53,7 @@ export class Ability implements CanActivate {
if (user?.roles?.length) {
if (
['/auth/logout', '/logout', '/auth/me', '/channel'].includes(
['/auth/logout', '/logout', '/auth/me', '/channel', '/i18n'].includes(
_parsedUrl.pathname,
)
) {

View File

@ -17,9 +17,11 @@
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"resolveJsonModule": true,
"esModuleInterop": true,
"paths": {
"@/*": ["src/*"]
}
}
},
"include": ["src/**/*.ts", "src/**/*.json", "test/**/*.ts"]
}

View File

@ -1,28 +0,0 @@
{
"title": {
"live-chat-tester": "Live Chat Tester"
},
"label": {
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}
}

View File

@ -1,28 +0,0 @@
{
"title": {
"offline": "Web Channel"
},
"label": {
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}
}

View File

@ -444,51 +444,9 @@
"delete": "Delete",
"role": "Role",
"owner": "Owner",
"app_secret": "Facebook App Secret",
"access_token": "Facebook Page Access Token",
"verify_token": "Webhook Verification Token",
"client_id": "Client ID",
"client_secret": "Client Secret",
"app_id": "ID of the Facebook Application",
"page_id": "ID of the Facebook Page",
"user_fields": "User fields to be retrieved (comma seperated)",
"mode": "Mode",
"mode_options": {
"emulator": "Emulator",
"live": "Live"
},
"get_started_button": "Enable `Get Started` button",
"composer_input_disabled": "Disable composer input",
"greeting_text": "Greeting Text",
"provider": "Provider",
"provider_options": {
"wit": "Wit.ai",
"rasa": "Rasa NLU"
},
"languages": "Available Languages",
"default_lang": "Default Language",
"secret": "Secret",
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma seperated)",
"channel": "Channel",
"entry": "Entry content",
"thumbnail": "Thumbnail",
@ -619,41 +577,13 @@
"reset": "Forgot your password?"
},
"help": {
"verify_token": "Token that you will returned to Facebook as part of the verification of the Webhook URL.",
"composer_input_disabled": "This means your bot can only be interacted with via the persistent menu, postbacks, buttons, and webviews.",
"get_started_button": "A bot's welcome screen can display a Get Started button. When this button is tapped, the Messenger Platform will send a postback event to your webhook (Payload = `GET_STARTED`).",
"greeting_text": "The greeting property of your bot's Messenger profile allows you to specify the greeting message people will see on the welcome screen of your bot. The welcome screen is displayed for people interacting with your bot for the first time.",
"fallback_message": "If no fallback block is selected, then one of these messages will be sent.",
"app_id": "Mandatory only if you intend to use Facebook Analytics",
"page_id": "Mandatory only if you intend to use Facebook Analytics",
"nlp_train": "You can train your chatbot by adding more examples",
"hit_enter_to_create": "Hit `enter` to create new",
"nlp_precision": "Given an entity, precision is likelihood to make the right prediction.",
"nlp_recall": "Recall is the likelihood to predict an entity out of all entities.",
"nlp_f1score": "The F1 score can be interpreted as a weighted average of the precision and recall",
"nlp_accuracy": "Accuracy score is the proportion of the correctly classified samples.",
"message_tag_shipping_update": "The shipping_update tag may only be used to provide a shipping status notification for a product that has already been purchased. For example, when the product is shipped, in-transit, delivered, or delayed. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_reservation_update": "The reservation_update tag may only be used to confirm updates to an existing reservation. For example, when there is a change in itinerary, location, or a cancellation (such as when a hotel booking is canceled, a car rental pick-up time changes, or a room upgrade is confirmed). This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_issue_resolution": "The issue_resolution tag may only be used to respond to a customer service issue surfaced in a Messenger conversation after a transaction has taken place. This tag is intended for use cases where the business requires more than 24 hours to resolve an issue and needs to give someone a status update and/or gather additional information. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements, nor can businesses use the tag to proactively message people to solicit feedback).",
"message_tag_appointment_update": "The appointment_update tag may only be used to provide updates about an existing appointment. For example, when there is a change in time, a location update or a cancellation (such as when a spa treatment is canceled, a real estate agent needs to meet you at a new location or a dental office proposes a new appointment time). This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_game_event": "The game_event tag may only be used to provide an update on user progression, a global event in a game or a live sporting event. For example, when a person\u2019s crops are ready to be collected, their building is finished, their daily tournament is about to start or their favorite soccer team is about to play. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_transportation_update": "The transportation_update tag may only be used to confirm updates to an existing reservation. For example, when there is a change in status of any flight, train or ferry reservation (such as \u201cride canceled\u201d, \u201ctrip started\u201d or \u201cferry arrived\u201d). This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_feature_functionality_update": "The feature_functionality_update tag may only be used to provide an update on new features or functionality that become available in a bot. For example, announcing the ability to talk to a live agent in a bot, or that the bot has a new skill. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_ticket_update": "The ticket_update tag may only be used to notify the message recipient of updates or reminders pertaining to an event for which the person has already confirmed attendance. For example, when you want to send a message about a change in time, a location update, a cancellation or a reminder for an upcoming event (such as when a concert is canceled, the venue has changed or a refund opportunity is available). This tag cannot be used for use cases beyond those listed above or for promotional content (ex: daily deals, coupons and discounts, or sale announcements).",
"message_tag_account_update": "The ACCOUNT_UPDATE tag may only be used to confirm updates to a user's account setting. For example, when there is a change in account settings and preferences of a user profile, notification of a password change, or membership expiration. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: promotion for signups, new account creations, or deals to extend subscriptions).",
"message_tag_payment_update": "The PAYMENT_UPDATE tag may be used to provide payment updates to existing transactions. For example, it can be used to send a receipt, out-of-stock, auction ended, refund, or a status change in an existing purchase transaction. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: any cross-sell / up-sell promotions, coupons, or deals to extend subscriptions).",
"message_tag_personal_finance_update": "The PERSONAL_FINANCE_UPDATE tag may be used to confirm a user's financial activity. For example, it can be used to send notifications on bill pay reminders, scheduled payments, receipts of payment, transfer of funds, or other transactional activities in financial services. This tag cannot be used for use cases beyond those listed above or for promotional content (ex: promotion for signups, or offers such as free trials for any financial products).",
"message_tag_pairing_update": "The pairing_update tag can be used to notify the message recipient that a pairing has been identified based on the recipient's prior request. Examples: Match has been confirmed in a dating app. User has confirmed an open parking spot for someone who previously requested one.",
"message_tag_application_update": "The application_update tag can be used to notify the message recipient of an update on the status of their application. Examples: Application is being reviewed. Application has been rejected.",
"message_tag_confirmed_event_reminder": "The confirmed_event_reminder tag can be used to send the message recipient reminders of a scheduled event for which a person is going to attend. Examples: Upcoming classes or events that a person has signed up for. Confirmation of attendance to an accepted event or appointment.",
"message_tag_community_alert": "The community_alert tag can be used to notify the message recipient of utility alerts, or safety checks in your community. Examples: Request a safety check. Notify of an emergency or utility alerts.",
"message_tag_non_promotional_subscription": "The non_promotional_subscription tag can be used to send non-promotional messages under the News, Productivity, and Personal Trackers categories described in the Messenger Platform's subscription messaging policy. You can apply for access to use this tag under the Page Settings > Messenger Platform. Use Cases: News. Productivity. Personal Trackers.",
"supported_message_type_non_promotional_subscription": "All message types and templates are supported, as long as the message adheres to the Messenger Platform's subscription messaging policy.",
"supported_message_type_issue_resolution": "Generic template and text messages are supported.",
"supported_message_type_others": "Only generic template messages are supported.",
"notification_type_regular": "Sound/Vibration",
"notification_type_silent_push": "On-screen notification only",
"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": {

View File

@ -1,28 +0,0 @@
{
"title": {
"live-chat-tester": "Testeur Live Chat"
},
"label": {
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}
}

View File

@ -1,28 +0,0 @@
{
"title": {
"offline": "Canal Web"
},
"label": {
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}
}

View File

@ -444,52 +444,10 @@
"delete": "Suppression",
"role": "Rôle",
"owner": "Propriétaire",
"app_secret": "Clé secrète Facebook",
"access_token": "Jeton daccès de la Page Facebook",
"verify_token": "Jeton de Verification de l'URL de rappel",
"client_id": "ID Client",
"client_secret": "Clé secrète Client",
"app_id": "ID de l'application Facebook",
"page_id": "ID de la page Facebook",
"user_fields": "Champs utilisateur à récupérer (séparé par des virgules)",
"mode": "Mode",
"mode_options": {
"emulator": "Emulateur",
"live": "Production"
},
"get_started_button": "Activer le bouton `Démarrer`",
"composer_input_disabled": "Désactiver la saisie du compositeur",
"greeting_text": "Texte de bienvenue",
"provider": "Fournisseur",
"provider_options": {
"wit": "Wit.ai",
"rasa": "Rasa NLU"
},
"languages": "Langues disponibles",
"default_lang": "Langue",
"global_fallback": "Activer le message de secours global?",
"secret": "Mot de passe",
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)",
"channel": "Canal",
"entry": "Contenu de l'entrée",
"thumbnail": "Aperçu",
@ -620,41 +578,13 @@
"reset": "Mot de passe oublié?"
},
"help": {
"verify_token": "Token que vous renverra Facebook dans le cadre de la vérification de lURL de rappel.",
"composer_input_disabled": "Cela signifie que votre robot ne peut interagir qu'avec le menu persistant, les valeurs de retours, les boutons et les Webviews.",
"get_started_button": "L'écran de bienvenue d'un chatbot peut afficher un bouton `Démarrer`. Lorsque ce bouton est appuyé, la plate-forme Messenger envoie un événement à votre webhook (Payload = `Démarrer`).",
"greeting_text": "Le texte de bienvenue de votre chatbot vous permet de spécifier le message d'accueil que les utilisateurs verront sur l'écran d'accueil de votre chatbot. L'écran de bienvenue s'affiche pour les personnes qui interagissent avec votre chatbot pour la première fois.",
"fallback_message": "Si aucun bloc de secours n'est spécifié, alors de ces messages sera envoyé.",
"app_id": "Obligatoire seulement si vous comptez utiliser Facebook Analytics",
"page_id": "Obligatoire seulement si vous comptez utiliser Facebook Analytics",
"nlp_train": "Vous pouvez entraîner votre chatbot en ajoutant plusieurs exemples",
"hit_enter_to_create": "Appuyez sur `Entrer` pour créer un nouveau",
"nlp_precision": "Pour une entité, la précision est la probabilité de faire la bonne prédiction.",
"nlp_recall": "Le rappel est la probabilité de reconnaître une entité parmi toutes les entités.",
"nlp_f1score": "Le score F1 peut être interprété comme une moyenne pondérée de la précision et du rappel",
"nlp_accuracy": "Le score de précision est la proportion des échantillons correctement classés.",
"message_tag_shipping_update": "Le tag shipping_update ne peut être utilisé que pour fournir une notification d'état d'expédition pour un produit déjà acheté. Par exemple, lorsque le produit est expédié, en transit, livré ou retardé. Cette étiquette ne peut pas être utilisée pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_reservation_update": "Le tag reservation_update ne peut être utilisé que pour confirmer les mises à jour d'une réservation existante. Par exemple, lorsqu'il y a un changement d'itinéraire, d'emplacement ou d'annulation (par exemple, lorsqu'une réservation d'hôtel est annulée, l'heure de prise en charge d'une location de voiture change ou une surclassement de chambre est confirmée). Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_issue_resolution": "Le tag issue_resolution ne peut être utilisé que pour répondre à un problème de service client apparu dans une conversation Messenger après qu'une transaction a eu lieu. Cette balise est destinée aux cas d'utilisation où l'entreprise a besoin de plus de 24 heures pour résoudre un problème et doit donner à quelqu'un une mise à jour du statut et / ou recueillir des informations supplémentaires. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux mentionnés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente, les entreprises ne peuvent pas utiliser le tag pour envoyer des commentaires proactifs).",
"message_tag_appointment_update": "Le tag appointment_update ne peut être utilisée que pour fournir des mises à jour sur un rendez-vous existant. Par exemple, lorsqu'il y a un changement dans le temps, une mise à jour ou une annulation (comme lorsqu'un spa est annulé, un agent immobilier doit vous rencontrer dans un nouvel endroit ou un cabinet dentaire propose une nouvelle heure de rendez-vous). Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_game_event": "Le tag game_event ne peut être utilisé que pour fournir une mise à jour sur la progression de l'utilisateur, un événement global dans un jeu ou un événement sportif en direct. Par exemple, quand les récoltes d'une personne sont prêtes à être récoltées, leur construction est terminée, leur tournoi quotidien est sur le point de commencer ou leur équipe de football préférée est sur le point de jouer. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_transportation_update": "Le tag transportation_update ne peut être utilisé que pour confirmer les mises à jour d'une réservation existante. Par exemple, en cas de changement de statut d'un vol, d'une réservation de train. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_feature_functionality_update": "Le tag feature_functionality_update ne peut être utilisé que pour fournir une mise à jour sur les nouvelles fonctionnalités ou fonctionnalités qui deviennent disponibles dans un bot. Par exemple, annoncer la possibilité de parler à un agent en direct dans un bot, ou que le bot a une nouvelle compétence. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente).",
"message_tag_ticket_update": "Le tag ticket_update ne peut être utilisé que pour informer le destinataire du message des mises à jour ou des rappels relatifs à un événement pour lequel la personne a déjà confirmé sa présence. Par exemple, lorsque vous souhaitez envoyer un message concernant un changement d'heure, une mise à jour d'emplacement, une annulation ou un rappel pour un événement à venir (par exemple, lorsqu'un concert est annulé, le lieu a changé ou une possibilité de remboursement est disponible). Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: offres quotidiennes, coupons et remises, ou annonces de vente)",
"message_tag_account_update": "Le tag account_update peut uniquement être utilisé pour confirmer les mises à jour du paramètre de compte d'un utilisateur. Par exemple, en cas de modification des paramètres et des préférences d'un profil utilisateur, de la notification d'un changement de mot de passe ou de l'expiration de l'adhésion. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: promotion pour les inscriptions, créations de nouveaux comptes ou offres pour étendre les abonnements).",
"message_tag_payment_update": "Le tag payment_update peut être utilisé pour fournir des mises à jour de paiement à des transactions existantes. Par exemple, il peut être utilisé pour envoyer un reçu, un rupture de stock, une enchère terminée, un remboursement ou un changement de statut dans une transaction d'achat existante. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: toute promotion de vente croisée / incitative, coupons, ou offres d'extension d'abonnements).",
"message_tag_personal_finance_update": "Le tag PERSONAL_FINANCE_UPDATE peut être utilisé pour confirmer l'activité financière d'un utilisateur. Par exemple, il peut être utilisé pour envoyer des notifications sur les rappels de paiement de factures, les paiements programmés, les reçus de paiement, les transferts de fonds ou d'autres activités transactionnelles dans les services financiers. Ce tag ne peut pas être utilisé pour des cas d'utilisation autres que ceux énumérés ci-dessus ou pour du contenu promotionnel (ex: promotion pour les inscriptions, ou des offres telles que des essais gratuits pour tout produit financier).",
"message_tag_pairing_update": "Le tag pairing_update peut être utilisé pour notifier au destinataire du message qu'un jumelage a été identifié en fonction de la demande préalable du destinataire. Exemples: Le jumelage a été confirmé dans une application de rencontres. L'utilisateur a confirmé une place de parking ouverte pour quelqu'un qui en avait précédemment demandé une.",
"message_tag_application_update": "Le tag application_update peut être utilisé pour informer le destinataire du message d'une mise à jour sur l'état de son application. Exemples: L'application est en cours de révision. La demande a été rejetée.",
"message_tag_confirmed_event_reminder": "Le tag confirmed_event_reminder peut être utilisé pour envoyer aux destinataires du message des rappels d'un événement planifié auquel une personne va participer. Exemples: Cours ou événements à venir pour lesquels une personne s'est inscrite. Confirmation de participation à un événement ou à un rendez-vous accepté.",
"message_tag_community_alert": "Le tag community_alert peut être utilisé pour informer le destinataire du message des alertes utilitaires ou des vérifications de sécurité dans votre communauté. Exemples: Demander un contrôle de sécurité. Avertir d'une urgence ou d'alertes d'utilité.",
"message_tag_non_promotional_subscription": "Le tag non_promotion_subscription peut être utilisé pour envoyer des messages non promotionnels dans les catégories Actualités, Productivité et Suivi personnel décrites dans la politique d'abonnement à la messagerie de la plate-forme Messenger. Vous pouvez demander l'accès pour utiliser ce tag, allez dans les paramètres de la page Plate-forme Messenger. Cas d'utilisation: Nouvelles. Productivité. Traqueurs personnels.",
"supported_message_type_non_promotional_subscription": "Tous les types de messages et modèles sont pris en charge, à condition que le message respecte la politique d'abonnement à la messagerie de la plate-forme Messenger.",
"supported_message_type_issue_resolution": "Le modèle générique et les messages texte sont supportés.",
"supported_message_type_others": "Seuls les messages de modèle générique sont pris en charge.",
"notification_type_regular": "Son/Vibration",
"notification_type_silent_push": "Notification à l'écran uniquement",
"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": {

View File

@ -10,6 +10,7 @@ import { createContext, ReactNode } from "react";
import { Progress } from "@/app-components/displays/Progress";
import { useLoadSettings } from "@/hooks/entities/auth-hooks";
import { useRemoteI18n } from "@/hooks/useRemoteI18n";
import { ISetting } from "@/types/setting.types";
export const SettingsContext = createContext<{
@ -27,6 +28,9 @@ export const SettingsProvider = ({
}: SettingsProviderProps): JSX.Element => {
const { data, isLoading } = useLoadSettings();
// Load API i18n Translations (extensions, ...)
useRemoteI18n();
if (isLoading) return <Progress />;
return (

View File

@ -0,0 +1,49 @@
/*
* Copyright © 2024 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.
* 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 { useEffect, useRef } from "react";
import i18n from "@/i18n/config";
import { useApiClient } from "./useApiClient";
import { useAuth } from "./useAuth";
export const useRemoteI18n = () => {
const { isAuthenticated } = useAuth();
const { apiClient } = useApiClient();
const isRemoteI18nLoaded = useRef(false);
useEffect(() => {
const fetchRemoteI18n = async () => {
try {
const additionalTranslations = await apiClient.fetchRemoteI18n();
// Assuming additionalTranslations is an object like { en: { translation: { key: 'value' } } }
Object.keys(additionalTranslations).forEach((lang) => {
Object.keys(additionalTranslations[lang]).forEach((namespace) => {
i18n.addResourceBundle(
lang,
namespace,
additionalTranslations[lang][namespace],
true,
true,
);
});
});
} catch (error) {
// eslint-disable-next-line no-console
console.error("Failed to fetch remote i18n translations:", error);
}
};
if (isAuthenticated && !isRemoteI18nLoaded.current) {
fetchRemoteI18n();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAuthenticated]);
};

View File

@ -26,14 +26,7 @@ i18n
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
ns: [
"translation",
"chatbot_settings.json",
"contact",
"nlp_settings",
"offline",
"live-chat-tester",
],
ns: ["translation", "chatbot_settings", "contact", "nlp_settings"],
interpolation: {
escapeValue: false,
},

View File

@ -32,6 +32,7 @@ export const ROUTES = {
CSRF: "/csrftoken",
BOTSTATS: "/botstats",
REFRESH_TRANSLATIONS: "/translation/refresh",
FETCH_REMOTE_I18N: "/i18n",
RESET: "/user/reset",
NLP_SAMPLE_IMPORT: "/nlpsample/import",
NLP_SAMPLE_PREDICT: "/nlpsample/message",
@ -190,6 +191,12 @@ export class ApiClient {
return data;
}
async fetchRemoteI18n() {
const { data } = await this.request.get(ROUTES.FETCH_REMOTE_I18N);
return data;
}
async reset(token: string, payload: IResetPayload) {
const { data } = await this.request.post<
IResetPayload,