mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: refactor extensions as npm packages (be brave 1)
This commit is contained in:
parent
4cf5c57053
commit
35cf78c523
@ -14,3 +14,4 @@ test
|
||||
*.mock.ts
|
||||
__mock__
|
||||
__test__
|
||||
.hexabot
|
||||
|
1
api/.gitignore
vendored
1
api/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.hexabot/
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
|
7
api/package-lock.json
generated
7
api/package-lock.json
generated
@ -11355,6 +11355,11 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hexabot-plugin-medmar": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hexabot-plugin-medmar/-/hexabot-plugin-medmar-2.0.1.tgz",
|
||||
"integrity": "sha512-qT2wG+vPjgBD9PZYl1EqzZPqVpoOrpldRUEDiwuzIKkIHEdfL6Q5Tr3rcgnYqFIPimVQhn/0tMgd1yQLD1dCPQ=="
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
@ -19437,4 +19442,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,12 +8,18 @@
|
||||
"scripts": {
|
||||
"preinstall": "node merge-extensions-deps.js",
|
||||
"postinstall": "patch-package",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"build:clean": "rm -rf src/.hexabot",
|
||||
"build:channels": "mkdir -p src/.hexabot/channels && cp -R node_modules/hexabot-channel-* src/.hexabot/channels/",
|
||||
"build:helpers": "mkdir -p src/.hexabot/helpers && cp -R node_modules/hexabot-helper-* src/.hexabot/helpers/",
|
||||
"build:plugins": "mkdir -p src/.hexabot/plugins && cp -R node_modules/hexabot-plugin-* src/.hexabot/plugins/",
|
||||
"build:extensions": "npm run build:channels && npm run build:helpers && npm run build:plugins",
|
||||
"build:prepare": "npm run build:clean && npm run build:extensions",
|
||||
"build": "npm run build:prepare && nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"doc": "npx @compodoc/compodoc --hideGenerator -p tsconfig.doc.json -s -r 9003 -w",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug 0.0.0.0:9229 --watch",
|
||||
"start:dev": "npm run build:prepare && nest start --watch",
|
||||
"start:debug": "npm run build:prepare && nest start --debug 0.0.0.0:9229 --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
|
@ -108,13 +108,13 @@ export class AttachmentService extends BaseService<Attachment> {
|
||||
await this.getStoragePlugin().uploadAvatar(picture);
|
||||
this.logger.log(
|
||||
`Profile picture uploaded successfully to ${
|
||||
this.getStoragePlugin().id
|
||||
this.getStoragePlugin().name
|
||||
}`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Error while uploading profile picture to ${
|
||||
this.getStoragePlugin().id
|
||||
this.getStoragePlugin().name
|
||||
}`,
|
||||
err,
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ export class ChannelController {
|
||||
getChannels(): { name: string }[] {
|
||||
return this.channelService.getAll().map((handler) => {
|
||||
return {
|
||||
name: handler.getChannel(),
|
||||
name: handler.getName(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
import { ChannelService } from './channel.service';
|
||||
|
||||
@ -20,7 +20,9 @@ export class ChannelMiddleware implements NestMiddleware {
|
||||
try {
|
||||
const [_, path, channelName] = req.path.split('/');
|
||||
if (path === 'webhook' && channelName) {
|
||||
const channel = this.channelService.getChannelHandler(channelName);
|
||||
const channel = this.channelService.getChannelHandler(
|
||||
`${channelName}-channel`,
|
||||
);
|
||||
if (channel) {
|
||||
return await channel.middleware(req, res, next);
|
||||
}
|
||||
|
@ -7,7 +7,12 @@
|
||||
*/
|
||||
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||
import {
|
||||
Global,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
RequestMethod,
|
||||
} from '@nestjs/common';
|
||||
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
|
||||
|
||||
import { AttachmentModule } from '@/attachment/attachment.module';
|
||||
@ -23,6 +28,7 @@ export interface ChannelModuleOptions {
|
||||
folder: string;
|
||||
}
|
||||
|
||||
@Global()
|
||||
@InjectDynamicProviders('dist/extensions/**/*.channel.js')
|
||||
@Module({
|
||||
controllers: [WebhookController, ChannelController],
|
||||
|
@ -23,10 +23,11 @@ import { SocketRequest } from '@/websocket/utils/socket-request';
|
||||
import { SocketResponse } from '@/websocket/utils/socket-response';
|
||||
|
||||
import ChannelHandler from './lib/Handler';
|
||||
import { ChannelName } from './types';
|
||||
|
||||
@Injectable()
|
||||
export class ChannelService {
|
||||
private registry: Map<string, ChannelHandler<string>> = new Map();
|
||||
private registry: Map<string, ChannelHandler<ChannelName>> = new Map();
|
||||
|
||||
constructor(
|
||||
private readonly logger: LoggerService,
|
||||
@ -40,7 +41,7 @@ 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<T extends string, C extends ChannelHandler<T>>(
|
||||
public setChannel<T extends ChannelName, C extends ChannelHandler<T>>(
|
||||
name: T,
|
||||
channel: C,
|
||||
) {
|
||||
@ -62,9 +63,9 @@ export class ChannelService {
|
||||
* @param name - The name of the channel to find.
|
||||
* @returns The channel handler associated with the specified name, or undefined if the channel is not found.
|
||||
*/
|
||||
public findChannel(name: string) {
|
||||
public findChannel(name: ChannelName) {
|
||||
return this.getAll().find((c) => {
|
||||
return c.getChannel() === name;
|
||||
return c.getName() === name;
|
||||
});
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ export class ChannelService {
|
||||
* @param channelName - The name of the channel (messenger, offline, ...).
|
||||
* @returns The handler for the specified channel.
|
||||
*/
|
||||
public getChannelHandler<T extends string, C extends ChannelHandler<T>>(
|
||||
public getChannelHandler<T extends ChannelName, C extends ChannelHandler<T>>(
|
||||
name: T,
|
||||
): C {
|
||||
const handler = this.registry.get(name);
|
||||
@ -93,7 +94,7 @@ export class ChannelService {
|
||||
* @returns A promise that resolves when the handler has processed the request.
|
||||
*/
|
||||
async handle(channel: string, req: Request, res: Response): Promise<void> {
|
||||
const handler = this.getChannelHandler(channel);
|
||||
const handler = this.getChannelHandler(`${channel}-channel`);
|
||||
handler.handle(req, res);
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ export default abstract class EventWrapper<
|
||||
toString() {
|
||||
return JSON.stringify(
|
||||
{
|
||||
handler: this._handler.getChannel(),
|
||||
handler: this._handler.getName(),
|
||||
channelData: this.getChannelData(),
|
||||
sender: this.getSender(),
|
||||
recipient: this.getRecipientForeignId(),
|
||||
|
@ -6,7 +6,9 @@
|
||||
* 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 path from 'path';
|
||||
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
||||
@ -17,35 +19,39 @@ import {
|
||||
} from '@/chat/schemas/types/message';
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { hyphenToUnderscore } from '@/utils/helpers/misc';
|
||||
import { Extension } from '@/utils/generics/extension';
|
||||
import { SocketRequest } from '@/websocket/utils/socket-request';
|
||||
import { SocketResponse } from '@/websocket/utils/socket-response';
|
||||
|
||||
import { ChannelService } from '../channel.service';
|
||||
import { ChannelSetting } from '../types';
|
||||
import { ChannelName, ChannelSetting } from '../types';
|
||||
|
||||
import EventWrapper from './EventWrapper';
|
||||
|
||||
@Injectable()
|
||||
export default abstract class ChannelHandler<N extends string = string> {
|
||||
private readonly name: N;
|
||||
|
||||
export default abstract class ChannelHandler<
|
||||
N extends ChannelName = ChannelName,
|
||||
>
|
||||
extends Extension
|
||||
implements OnModuleInit
|
||||
{
|
||||
private readonly settings: ChannelSetting<N>[];
|
||||
|
||||
constructor(
|
||||
name: N,
|
||||
settings: ChannelSetting<N>[],
|
||||
protected readonly settingService: SettingService,
|
||||
private readonly channelService: ChannelService,
|
||||
protected readonly logger: LoggerService,
|
||||
) {
|
||||
this.name = name;
|
||||
this.settings = settings;
|
||||
super(name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
this.settings = require(path.join(this.getPath(), 'settings')).default;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
async onModuleInit() {
|
||||
await super.onModuleInit();
|
||||
this.channelService.setChannel(
|
||||
this.getChannel(),
|
||||
this.getName() as ChannelName,
|
||||
this as unknown as ChannelHandler<N>,
|
||||
);
|
||||
this.setup();
|
||||
@ -53,7 +59,7 @@ export default abstract class ChannelHandler<N extends string = string> {
|
||||
|
||||
async setup() {
|
||||
await this.settingService.seedIfNotExist(
|
||||
this.getChannel(),
|
||||
this.getName(),
|
||||
this.settings.map((s, i) => ({
|
||||
...s,
|
||||
weight: i + 1,
|
||||
@ -62,22 +68,6 @@ export default abstract class ChannelHandler<N extends string = string> {
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel's name
|
||||
* @returns Channel's name
|
||||
*/
|
||||
getChannel() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel's group
|
||||
* @returns Channel's group
|
||||
*/
|
||||
protected getGroup() {
|
||||
return hyphenToUnderscore(this.getChannel()) as ChannelSetting<N>['group'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel's settings
|
||||
* @returns Channel's settings
|
||||
@ -85,7 +75,7 @@ export default abstract class ChannelHandler<N extends string = 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];
|
||||
return settings[this.getNamespace() as keyof Settings] as Settings[S];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ export const subscriberInstance: Subscriber = {
|
||||
lastvisit: new Date(),
|
||||
retainedFrom: new Date(),
|
||||
channel: {
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
labels: [],
|
||||
...modelInstance,
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { SettingCreateDto } from '@/setting/dto/setting.dto';
|
||||
|
||||
export type ChannelName = `${string}-channel`;
|
||||
|
||||
export type ChannelSetting<N extends string = string> = Omit<
|
||||
SettingCreateDto,
|
||||
'group' | 'weight'
|
||||
|
@ -27,7 +27,7 @@ import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import { BaseBlockPlugin } from '@/plugins/base-block-plugin';
|
||||
import { PluginService } from '@/plugins/plugins.service';
|
||||
import { PluginType } from '@/plugins/types';
|
||||
import { PluginName, PluginType } from '@/plugins/types';
|
||||
import { UserService } from '@/user/services/user.service';
|
||||
import { BaseController } from '@/utils/generics/base-controller';
|
||||
import { DeleteResult } from '@/utils/generics/base-repository';
|
||||
@ -85,20 +85,23 @@ export class BlockController extends BaseController<
|
||||
/**
|
||||
* Retrieves a custom block settings for a specific plugin.
|
||||
*
|
||||
* @param pluginId - The name of the plugin for which settings are to be retrieved.
|
||||
* @param pluginName - The name of the plugin for which settings are to be retrieved.
|
||||
*
|
||||
* @returns An array containing the settings of the specified plugin.
|
||||
*/
|
||||
@Get('customBlocks/settings')
|
||||
findSettings(@Query('plugin') pluginId: string) {
|
||||
findSettings(@Query('plugin') pluginName: PluginName) {
|
||||
try {
|
||||
if (!pluginId) {
|
||||
if (!pluginName) {
|
||||
throw new BadRequestException(
|
||||
'Plugin id must be supplied as a query param',
|
||||
);
|
||||
}
|
||||
|
||||
const plugin = this.pluginsService.getPlugin(PluginType.block, pluginId);
|
||||
const plugin = this.pluginsService.getPlugin(
|
||||
PluginType.block,
|
||||
pluginName,
|
||||
);
|
||||
|
||||
if (!plugin) {
|
||||
throw new NotFoundException('Plugin Not Found');
|
||||
@ -122,11 +125,12 @@ export class BlockController extends BaseController<
|
||||
const plugins = this.pluginsService
|
||||
.getAllByType(PluginType.block)
|
||||
.map((p) => ({
|
||||
id: p.id,
|
||||
id: p.getName(),
|
||||
namespace: p.getNamespace(),
|
||||
template: {
|
||||
...p.template,
|
||||
message: {
|
||||
plugin: p.id,
|
||||
plugin: p.name,
|
||||
args: p.settings.reduce(
|
||||
(acc, setting) => {
|
||||
acc[setting.label] = setting.value;
|
||||
|
@ -6,8 +6,10 @@
|
||||
* 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 { ChannelName } from '@/channel/types';
|
||||
|
||||
interface BaseChannelData {
|
||||
name: string; // channel name
|
||||
name: ChannelName; // channel name
|
||||
isSocket?: boolean;
|
||||
type?: any; //TODO: type has to be checked
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* 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 { ChannelName } from '@/channel/types';
|
||||
import { Nlp } from '@/helper/types';
|
||||
|
||||
import { Subscriber } from '../subscriber.schema';
|
||||
@ -13,7 +14,7 @@ import { Subscriber } from '../subscriber.schema';
|
||||
import { Payload } from './quick-reply';
|
||||
|
||||
export interface Context {
|
||||
channel?: string;
|
||||
channel?: ChannelName;
|
||||
text?: string;
|
||||
payload?: Payload | string;
|
||||
nlp?: Nlp.ParseEntities | null;
|
||||
|
@ -222,7 +222,7 @@ describe('BlockService', () => {
|
||||
|
||||
describe('match', () => {
|
||||
const handlerMock = {
|
||||
getChannel: jest.fn(() => OFFLINE_CHANNEL_NAME),
|
||||
getName: jest.fn(() => OFFLINE_CHANNEL_NAME),
|
||||
} as any as OfflineHandler;
|
||||
const offlineEventGreeting = new OfflineEventWrapper(
|
||||
handlerMock,
|
||||
@ -502,7 +502,7 @@ describe('BlockService', () => {
|
||||
describe('processText', () => {
|
||||
const context: Context = {
|
||||
...contextGetStartedInstance,
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: '',
|
||||
payload: undefined,
|
||||
nlp: { entities: [] },
|
||||
|
@ -17,7 +17,7 @@ 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 { PluginType } from '@/plugins/types';
|
||||
import { PluginName, PluginType } from '@/plugins/types';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { BaseService } from '@/utils/generics/base-service';
|
||||
import { getRandom } from '@/utils/helpers/safeRandom';
|
||||
@ -71,7 +71,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
||||
const payload = event.getPayload();
|
||||
|
||||
// Perform a filter on the specific channels
|
||||
const channel = event.getHandler().getChannel();
|
||||
const channel = event.getHandler().getName();
|
||||
blocks = blocks.filter((b) => {
|
||||
return (
|
||||
!b.trigger_channels ||
|
||||
@ -593,7 +593,7 @@ export class BlockService extends BaseService<Block, BlockPopulate, BlockFull> {
|
||||
} else if (blockMessage && 'plugin' in blockMessage) {
|
||||
const plugin = this.pluginService.findPlugin(
|
||||
PluginType.block,
|
||||
blockMessage.plugin,
|
||||
blockMessage.plugin as PluginName,
|
||||
);
|
||||
// Process custom plugin block
|
||||
try {
|
||||
|
@ -224,7 +224,7 @@ describe('BlockService', () => {
|
||||
nlp: null,
|
||||
payload: null,
|
||||
attempt: 0,
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: offlineEventText.data.text,
|
||||
},
|
||||
});
|
||||
@ -283,7 +283,7 @@ describe('BlockService', () => {
|
||||
nlp: null,
|
||||
payload: null,
|
||||
attempt: 0,
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: offlineEventText.data.text,
|
||||
},
|
||||
});
|
||||
|
@ -246,7 +246,7 @@ export class ChatService {
|
||||
this.eventEmitter.emit('hook:stats:entry', 'new_users', 'New users');
|
||||
subscriberData.channel = {
|
||||
...event.getChannelData(),
|
||||
name: handler.getChannel(),
|
||||
name: handler.getName(),
|
||||
};
|
||||
subscriber = await this.subscriberService.create(subscriberData);
|
||||
} else {
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||
import { ChannelName } from '@/channel/types';
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import { BaseService } from '@/utils/generics/base-service';
|
||||
|
||||
@ -69,7 +70,7 @@ export class ConversationService extends BaseService<
|
||||
const msgType = event.getMessageType();
|
||||
const profile = event.getSender();
|
||||
// Capture channel specific context data
|
||||
convo.context.channel = event.getHandler().getChannel();
|
||||
convo.context.channel = event.getHandler().getName() as ChannelName;
|
||||
convo.context.text = event.getText();
|
||||
convo.context.payload = event.getPayload();
|
||||
convo.context.nlp = event.getNLP();
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"live_chat_tester": "Live Chat Tester"
|
||||
"live_chat_tester_channel": "Live Chat Tester"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"live_chat_tester": "Testeur Live Chat"
|
||||
"live_chat_tester_channel": "Testeur Live Chat"
|
||||
}
|
||||
|
@ -21,10 +21,7 @@ import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import BaseWebChannelHandler from '../offline/base-web-channel';
|
||||
|
||||
import {
|
||||
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
|
||||
LIVE_CHAT_TEST_CHANNEL_NAME,
|
||||
} from './settings';
|
||||
import { LIVE_CHAT_TEST_CHANNEL_NAME } from './settings';
|
||||
|
||||
@Injectable()
|
||||
export default class LiveChatTesterHandler extends BaseWebChannelHandler<
|
||||
@ -44,7 +41,6 @@ export default class LiveChatTesterHandler extends BaseWebChannelHandler<
|
||||
) {
|
||||
super(
|
||||
LIVE_CHAT_TEST_CHANNEL_NAME,
|
||||
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
|
||||
settingService,
|
||||
channelService,
|
||||
logger,
|
||||
@ -57,4 +53,8 @@ export default class LiveChatTesterHandler extends BaseWebChannelHandler<
|
||||
websocketGateway,
|
||||
);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
return __dirname;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
DEFAULT_LIVE_CHAT_TEST_SETTINGS,
|
||||
import DEFAULT_LIVE_CHAT_TEST_SETTINGS, {
|
||||
LIVE_CHAT_TEST_GROUP_NAME,
|
||||
} from './settings';
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "hexabot-channel-live-chat-tester",
|
||||
"version": "2.0.0",
|
||||
"description": "The Web Channel Extension for Hexabot Chatbot / Agent Builder for website integration",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
@ -12,11 +12,11 @@ import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
import { Offline } from '../offline/types';
|
||||
|
||||
export const LIVE_CHAT_TEST_CHANNEL_NAME = 'live-chat-tester';
|
||||
export const LIVE_CHAT_TEST_CHANNEL_NAME = 'live-chat-tester-channel';
|
||||
|
||||
export const LIVE_CHAT_TEST_GROUP_NAME = 'live_chat_tester';
|
||||
export const LIVE_CHAT_TEST_GROUP_NAME = 'live_chat_tester_channel';
|
||||
|
||||
export const DEFAULT_LIVE_CHAT_TEST_SETTINGS = [
|
||||
export default [
|
||||
{
|
||||
group: LIVE_CHAT_TEST_GROUP_NAME,
|
||||
label: Offline.SettingLabel.verification_token,
|
||||
|
@ -131,7 +131,7 @@ describe('Offline Handler', () => {
|
||||
|
||||
it('should have correct name', () => {
|
||||
expect(handler).toBeDefined();
|
||||
expect(handler.getChannel()).toEqual('offline');
|
||||
expect(handler.getName()).toEqual('offline-channel');
|
||||
});
|
||||
|
||||
it('should format text properly', () => {
|
||||
@ -192,7 +192,7 @@ describe('Offline Handler', () => {
|
||||
agent: req.headers['user-agent'],
|
||||
ipAddress: '0.0.0.0',
|
||||
isSocket: false,
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
country: '',
|
||||
first_name: req.query.first_name,
|
||||
|
@ -22,7 +22,7 @@ import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||
import { ChannelService } from '@/channel/channel.service';
|
||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||
import ChannelHandler from '@/channel/lib/Handler';
|
||||
import { ChannelSetting } from '@/channel/types';
|
||||
import { ChannelName } from '@/channel/types';
|
||||
import { MessageCreateDto } from '@/chat/dto/message.dto';
|
||||
import { SubscriberCreateDto } from '@/chat/dto/subscriber.dto';
|
||||
import { VIEW_MORE_PAYLOAD } from '@/chat/helpers/constants';
|
||||
@ -63,12 +63,11 @@ import { Offline } from './types';
|
||||
import OfflineEventWrapper from './wrapper';
|
||||
|
||||
@Injectable()
|
||||
export default class BaseWebChannelHandler<
|
||||
N extends string,
|
||||
export default abstract class BaseWebChannelHandler<
|
||||
N extends ChannelName,
|
||||
> extends ChannelHandler<N> {
|
||||
constructor(
|
||||
name: N,
|
||||
settings: ChannelSetting<N>[],
|
||||
settingService: SettingService,
|
||||
channelService: ChannelService,
|
||||
logger: LoggerService,
|
||||
@ -80,7 +79,7 @@ export default class BaseWebChannelHandler<
|
||||
protected readonly menuService: MenuService,
|
||||
private readonly websocketGateway: WebsocketGateway,
|
||||
) {
|
||||
super(name, settings, settingService, channelService, logger);
|
||||
super(name, settingService, channelService, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +101,7 @@ export default class BaseWebChannelHandler<
|
||||
const settings = await this.getSettings();
|
||||
const handshake = client.handshake;
|
||||
const { channel } = handshake.query;
|
||||
if (channel !== this.getChannel()) {
|
||||
if (channel !== this.getName()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -464,7 +463,7 @@ export default class BaseWebChannelHandler<
|
||||
retainedFrom: new Date(),
|
||||
channel: {
|
||||
...channelData,
|
||||
name: this.getChannel(),
|
||||
name: this.getName() as ChannelName,
|
||||
},
|
||||
language: '',
|
||||
locale: '',
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"offline": "Canal Web"
|
||||
"offline_channel": "Web Channel"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"live_chat_tester": "Testeur Live Chat"
|
||||
"offline_channel": "Canal Web"
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { SettingService } from '@/setting/services/setting.service';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import BaseWebChannelHandler from './base-web-channel';
|
||||
import { DEFAULT_OFFLINE_SETTINGS, OFFLINE_CHANNEL_NAME } from './settings';
|
||||
import { OFFLINE_CHANNEL_NAME } from './settings';
|
||||
|
||||
@Injectable()
|
||||
export default class OfflineHandler extends BaseWebChannelHandler<
|
||||
@ -40,7 +40,6 @@ export default class OfflineHandler extends BaseWebChannelHandler<
|
||||
) {
|
||||
super(
|
||||
OFFLINE_CHANNEL_NAME,
|
||||
DEFAULT_OFFLINE_SETTINGS,
|
||||
settingService,
|
||||
channelService,
|
||||
logger,
|
||||
@ -53,4 +52,8 @@ export default class OfflineHandler extends BaseWebChannelHandler<
|
||||
websocketGateway,
|
||||
);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
return __dirname;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DEFAULT_OFFLINE_SETTINGS, OFFLINE_GROUP_NAME } from './settings';
|
||||
import DEFAULT_OFFLINE_SETTINGS, { OFFLINE_GROUP_NAME } from './settings';
|
||||
|
||||
declare global {
|
||||
interface Settings extends SettingTree<typeof DEFAULT_OFFLINE_SETTINGS> {}
|
||||
|
7
api/src/extensions/channels/offline/package.json
Normal file
7
api/src/extensions/channels/offline/package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "hexabot-channel-offline",
|
||||
"version": "2.0.0",
|
||||
"description": "The Web Channel Extension for Hexabot Chatbot / Agent Builder for website integration",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
@ -11,11 +11,11 @@ import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
import { Offline } from './types';
|
||||
|
||||
export const OFFLINE_CHANNEL_NAME = 'offline' as const;
|
||||
export const OFFLINE_CHANNEL_NAME = 'offline-channel' as const;
|
||||
|
||||
export const OFFLINE_GROUP_NAME = OFFLINE_CHANNEL_NAME;
|
||||
export const OFFLINE_GROUP_NAME = 'offline_channel';
|
||||
|
||||
export const DEFAULT_OFFLINE_SETTINGS = [
|
||||
export default [
|
||||
{
|
||||
group: OFFLINE_GROUP_NAME,
|
||||
label: Offline.SettingLabel.verification_token,
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||
import { ChannelName } from '@/channel/types';
|
||||
import {
|
||||
AttachmentForeignKey,
|
||||
AttachmentPayload,
|
||||
@ -67,7 +68,8 @@ type OfflineEventAdapter =
|
||||
};
|
||||
|
||||
export default class OfflineEventWrapper<
|
||||
T extends BaseWebChannelHandler<string> = BaseWebChannelHandler<string>,
|
||||
T extends
|
||||
BaseWebChannelHandler<ChannelName> = BaseWebChannelHandler<ChannelName>,
|
||||
> extends EventWrapper<OfflineEventAdapter, Offline.Event> {
|
||||
/**
|
||||
* Constructor : channel's event wrapper
|
||||
|
@ -58,7 +58,7 @@ describe('Core NLU Helper', () => {
|
||||
provide: SettingService,
|
||||
useValue: {
|
||||
getSettings: jest.fn(() => ({
|
||||
core_nlu: {
|
||||
core_nlu_helper: {
|
||||
endpoint: 'path',
|
||||
token: 'token',
|
||||
threshold: '0.5',
|
||||
@ -121,7 +121,7 @@ describe('Core NLU Helper', () => {
|
||||
true,
|
||||
);
|
||||
const settings = await settingService.getSettings();
|
||||
const threshold = settings.core_nlu.threshold;
|
||||
const threshold = settings.core_nlu_helper.threshold;
|
||||
const thresholdGuess = {
|
||||
entities: nlpBestGuess.entities.filter(
|
||||
(g) =>
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"core_nlu": "Core NLU Engine"
|
||||
"core_nlu_helper": "Core NLU Engine"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"core_nlu": "Core NLU Engine"
|
||||
"core_nlu_helper": "Core NLU Engine"
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CORE_NLU_HELPER_GROUP, CORE_NLU_HELPER_SETTINGS } from './settings';
|
||||
import CORE_NLU_HELPER_SETTINGS, { CORE_NLU_HELPER_GROUP } from './settings';
|
||||
|
||||
declare global {
|
||||
interface Settings extends SettingTree<typeof CORE_NLU_HELPER_SETTINGS> {}
|
||||
|
@ -20,7 +20,7 @@ import { NlpValue } from '@/nlp/schemas/nlp-value.schema';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { buildURL } from '@/utils/helpers/URL';
|
||||
|
||||
import { CORE_NLU_HELPER_NAME, CORE_NLU_HELPER_SETTINGS } from './settings';
|
||||
import { CORE_NLU_HELPER_NAME } from './settings';
|
||||
import { NlpParseResultType, RasaNlu } from './types';
|
||||
|
||||
@Injectable()
|
||||
@ -34,13 +34,11 @@ export default class CoreNluHelper extends BaseNlpHelper<
|
||||
private readonly httpService: HttpService,
|
||||
private readonly languageService: LanguageService,
|
||||
) {
|
||||
super(
|
||||
CORE_NLU_HELPER_NAME,
|
||||
CORE_NLU_HELPER_SETTINGS,
|
||||
settingService,
|
||||
helperService,
|
||||
logger,
|
||||
);
|
||||
super(CORE_NLU_HELPER_NAME, settingService, helperService, logger);
|
||||
}
|
||||
|
||||
getPath() {
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "hexabot-core-nlu",
|
||||
"name": "hexabot-helper-core-nlu",
|
||||
"version": "2.0.0",
|
||||
"description": "The Core NLU Helper Extension for Hexabot Chatbot / Agent Builder to enable the Intent Classification and Language Detection",
|
||||
"dependencies": {},
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import { HelperSetting } from '@/helper/types';
|
||||
import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
export const CORE_NLU_HELPER_NAME = 'core-nlu';
|
||||
export const CORE_NLU_HELPER_NAME = 'core-nlu-helper';
|
||||
|
||||
export const CORE_NLU_HELPER_GROUP = 'core_nlu';
|
||||
export const CORE_NLU_HELPER_GROUP = 'core_nlu_helper';
|
||||
|
||||
export const CORE_NLU_HELPER_SETTINGS = [
|
||||
export default [
|
||||
{
|
||||
group: CORE_NLU_HELPER_GROUP,
|
||||
label: 'endpoint',
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"ollama": "Ollama"
|
||||
"ollama_helper": "Ollama"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"ollama": "Ollama"
|
||||
"ollama_helper": "Ollama"
|
||||
}
|
||||
|
2
api/src/extensions/helpers/ollama/index.d.ts
vendored
2
api/src/extensions/helpers/ollama/index.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { OLLAMA_HELPER_GROUP, OLLAMA_HELPER_SETTINGS } from './settings';
|
||||
import OLLAMA_HELPER_SETTINGS, { OLLAMA_HELPER_GROUP } from './settings';
|
||||
|
||||
declare global {
|
||||
interface Settings extends SettingTree<typeof OLLAMA_HELPER_SETTINGS> {}
|
||||
|
@ -17,7 +17,7 @@ import { LoggerService } from '@/logger/logger.service';
|
||||
import { Setting } from '@/setting/schemas/setting.schema';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
|
||||
import { OLLAMA_HELPER_NAME, OLLAMA_HELPER_SETTINGS } from './settings';
|
||||
import { OLLAMA_HELPER_NAME } from './settings';
|
||||
|
||||
@Injectable()
|
||||
export default class OllamaLlmHelper
|
||||
@ -36,13 +36,11 @@ export default class OllamaLlmHelper
|
||||
helperService: HelperService,
|
||||
protected readonly logger: LoggerService,
|
||||
) {
|
||||
super(
|
||||
OLLAMA_HELPER_NAME,
|
||||
OLLAMA_HELPER_SETTINGS,
|
||||
settingService,
|
||||
helperService,
|
||||
logger,
|
||||
);
|
||||
super('ollama-helper', settingService, helperService, logger);
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
async onApplicationBootstrap() {
|
||||
@ -51,7 +49,7 @@ export default class OllamaLlmHelper
|
||||
this.client = new Ollama({ host: settings.api_url });
|
||||
}
|
||||
|
||||
@OnEvent('hook:ollama:api_url')
|
||||
@OnEvent('hook:ollama_helper:api_url')
|
||||
handleApiUrlChange(setting: Setting) {
|
||||
this.client = new Ollama({ host: setting.value });
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { HelperSetting } from '@/helper/types';
|
||||
import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
export const OLLAMA_HELPER_NAME = 'ollama';
|
||||
export const OLLAMA_HELPER_NAME = 'ollama-helper';
|
||||
|
||||
export const OLLAMA_HELPER_GROUP = 'ollama';
|
||||
export const OLLAMA_HELPER_GROUP: HyphenToUnderscore<
|
||||
typeof OLLAMA_HELPER_NAME
|
||||
> = 'ollama_helper';
|
||||
|
||||
export const OLLAMA_HELPER_SETTINGS = [
|
||||
export default [
|
||||
{
|
||||
label: 'api_url',
|
||||
group: OLLAMA_HELPER_GROUP,
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"ollama": "Ollama"
|
||||
"ollama_plugin": "Ollama Plugin"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"ollama": "Ollama"
|
||||
"ollama_plugin": "Ollama Plugin"
|
||||
}
|
||||
|
@ -14,14 +14,13 @@ import { HelperType } from '@/helper/types';
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import { BaseBlockPlugin } from '@/plugins/base-block-plugin';
|
||||
import { PluginService } from '@/plugins/plugins.service';
|
||||
import { PluginBlockTemplate } from '@/plugins/types';
|
||||
|
||||
import { OLLAMA_PLUGIN_SETTINGS } from './settings';
|
||||
import SETTINGS from './settings';
|
||||
|
||||
@Injectable()
|
||||
export class OllamaPlugin extends BaseBlockPlugin<
|
||||
typeof OLLAMA_PLUGIN_SETTINGS
|
||||
> {
|
||||
public readonly settings = OLLAMA_PLUGIN_SETTINGS;
|
||||
export class OllamaPlugin extends BaseBlockPlugin<typeof SETTINGS> {
|
||||
template: PluginBlockTemplate = { name: 'Ollama Plugin' };
|
||||
|
||||
constructor(
|
||||
pluginService: PluginService,
|
||||
@ -30,12 +29,11 @@ export class OllamaPlugin extends BaseBlockPlugin<
|
||||
private contentService: ContentService,
|
||||
private messageService: MessageService,
|
||||
) {
|
||||
super('ollama', OLLAMA_PLUGIN_SETTINGS, pluginService);
|
||||
super('ollama-plugin', pluginService);
|
||||
}
|
||||
|
||||
this.template = { name: 'Ollama Plugin' };
|
||||
this.effects = {
|
||||
onStoreContextData: () => {},
|
||||
};
|
||||
getPath(): string {
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
async process(block: Block, context: Context, _convId: string) {
|
||||
|
@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "hexabot-ollama",
|
||||
"name": "hexabot-plugin-ollama",
|
||||
"version": "2.0.0",
|
||||
"description": "The Ollama Plugin Extension for Hexabot Chatbot / Agent Builder that provides a custom block for Generative AI + RAG",
|
||||
"dependencies": {},
|
||||
"extensions": {
|
||||
"dependencies": {
|
||||
"hexabot-helper-ollama": "2.0.0"
|
||||
},
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { PluginSetting } from '@/plugins/types';
|
||||
import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
export const OLLAMA_PLUGIN_SETTINGS = [
|
||||
export default [
|
||||
{
|
||||
label: 'model',
|
||||
group: 'default',
|
||||
|
@ -12,7 +12,7 @@ import { LoggerService } from '@/logger/logger.service';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
|
||||
import BaseHelper from './lib/base-helper';
|
||||
import { HelperRegistry, HelperType, TypeOfHelper } from './types';
|
||||
import { HelperName, HelperRegistry, HelperType, TypeOfHelper } from './types';
|
||||
|
||||
@Injectable()
|
||||
export class HelperService {
|
||||
@ -33,7 +33,7 @@ export class HelperService {
|
||||
*
|
||||
* @param name - The helper to be registered.
|
||||
*/
|
||||
public register<H extends BaseHelper<string>>(helper: H) {
|
||||
public register<H extends BaseHelper>(helper: H) {
|
||||
const helpers = this.registry.get(helper.getType());
|
||||
helpers.set(helper.getName(), helper);
|
||||
this.logger.log(`Helper "${helper.getName()}" has been registered!`);
|
||||
@ -47,7 +47,7 @@ export class HelperService {
|
||||
*
|
||||
* @returns - The helper
|
||||
*/
|
||||
public get<T extends HelperType>(type: T, name: string) {
|
||||
public get<T extends HelperType>(type: T, name: HelperName) {
|
||||
const helpers = this.registry.get(type);
|
||||
|
||||
if (!helpers.has(name)) {
|
||||
@ -67,6 +67,16 @@ export class HelperService {
|
||||
return Array.from(helpers.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered helpers as an array.
|
||||
*
|
||||
* @returns An array containing all the registered helpers.
|
||||
*/
|
||||
public getAll(): BaseHelper[] {
|
||||
return Array.from(this.registry.values()) // Get all the inner maps
|
||||
.flatMap((innerMap) => Array.from(innerMap.values())); // Flatten and get the values from each inner map
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a helper by class.
|
||||
*
|
||||
@ -100,7 +110,7 @@ export class HelperService {
|
||||
|
||||
const defaultHelper = this.get(
|
||||
HelperType.NLU,
|
||||
settings.chatbot_settings.default_nlu_helper,
|
||||
settings.chatbot_settings.default_nlu_helper as HelperName,
|
||||
);
|
||||
|
||||
if (!defaultHelper) {
|
||||
@ -120,7 +130,7 @@ export class HelperService {
|
||||
|
||||
const defaultHelper = this.get(
|
||||
HelperType.LLM,
|
||||
settings.chatbot_settings.default_llm_helper,
|
||||
settings.chatbot_settings.default_llm_helper as HelperName,
|
||||
);
|
||||
|
||||
if (!defaultHelper) {
|
||||
|
@ -6,33 +6,37 @@
|
||||
* 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 { LoggerService } from '@nestjs/common';
|
||||
import path from 'path';
|
||||
|
||||
import { LoggerService, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { hyphenToUnderscore } from '@/utils/helpers/misc';
|
||||
import { Extension } from '@/utils/generics/extension';
|
||||
|
||||
import { HelperService } from '../helper.service';
|
||||
import { HelperSetting, HelperType } from '../types';
|
||||
|
||||
export default abstract class BaseHelper<N extends string = string> {
|
||||
protected readonly name: N;
|
||||
import { HelperName, HelperSetting, HelperType } from '../types';
|
||||
|
||||
export default abstract class BaseHelper<N extends HelperName = HelperName>
|
||||
extends Extension
|
||||
implements OnModuleInit
|
||||
{
|
||||
protected readonly settings: HelperSetting<N>[] = [];
|
||||
|
||||
protected abstract type: HelperType;
|
||||
|
||||
constructor(
|
||||
name: N,
|
||||
settings: HelperSetting<N>[],
|
||||
protected readonly settingService: SettingService,
|
||||
protected readonly helperService: HelperService,
|
||||
protected readonly logger: LoggerService,
|
||||
) {
|
||||
this.name = name;
|
||||
this.settings = settings;
|
||||
super(name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
this.settings = require(path.join(this.getPath(), 'settings')).default;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
async onModuleInit() {
|
||||
await super.onModuleInit();
|
||||
this.helperService.register(this);
|
||||
this.setup();
|
||||
}
|
||||
@ -47,23 +51,6 @@ export default abstract class BaseHelper<N extends string = string> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the helper's name
|
||||
*
|
||||
* @returns Helper's name
|
||||
*/
|
||||
public getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the helper's group
|
||||
* @returns Helper's group
|
||||
*/
|
||||
protected getGroup() {
|
||||
return hyphenToUnderscore(this.getName()) as HelperSetting<N>['group'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the helper's type
|
||||
*
|
||||
@ -81,6 +68,6 @@ export default abstract class BaseHelper<N extends string = 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];
|
||||
return settings[this.getNamespace() as keyof Settings] as Settings[S];
|
||||
}
|
||||
}
|
||||
|
@ -11,23 +11,22 @@ import { LoggerService } from '@/logger/logger.service';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
|
||||
import { HelperService } from '../helper.service';
|
||||
import { HelperSetting, HelperType } from '../types';
|
||||
import { HelperName, HelperType } from '../types';
|
||||
|
||||
import BaseHelper from './base-helper';
|
||||
|
||||
export default abstract class BaseLlmHelper<
|
||||
N extends string,
|
||||
N extends HelperName = HelperName,
|
||||
> extends BaseHelper<N> {
|
||||
protected readonly type: HelperType = HelperType.LLM;
|
||||
|
||||
constructor(
|
||||
name: N,
|
||||
settings: HelperSetting<N>[],
|
||||
settingService: SettingService,
|
||||
helperService: HelperService,
|
||||
logger: LoggerService,
|
||||
) {
|
||||
super(name, settings, settingService, helperService, logger);
|
||||
super(name, settingService, helperService, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,23 +23,23 @@ import {
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
|
||||
import { HelperService } from '../helper.service';
|
||||
import { HelperSetting, HelperType, Nlp } from '../types';
|
||||
import { HelperName, HelperType, Nlp } from '../types';
|
||||
|
||||
import BaseHelper from './base-helper';
|
||||
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
export default abstract class BaseNlpHelper<
|
||||
N extends string,
|
||||
N extends HelperName = HelperName,
|
||||
> extends BaseHelper<N> {
|
||||
protected readonly type: HelperType = HelperType.NLU;
|
||||
|
||||
constructor(
|
||||
name: N,
|
||||
settings: HelperSetting<N>[],
|
||||
settingService: SettingService,
|
||||
helperService: HelperService,
|
||||
logger: LoggerService,
|
||||
) {
|
||||
super(name, settings, settingService, helperService, logger);
|
||||
super(name, settingService, helperService, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,10 +29,12 @@ export enum HelperType {
|
||||
UTIL = 'util',
|
||||
}
|
||||
|
||||
export type HelperName = `${string}-helper`;
|
||||
|
||||
export type TypeOfHelper<T extends HelperType> = T extends HelperType.LLM
|
||||
? BaseLlmHelper<string>
|
||||
? BaseLlmHelper<HelperName>
|
||||
: T extends HelperType.NLU
|
||||
? BaseNlpHelper<string>
|
||||
? BaseNlpHelper<HelperName>
|
||||
: BaseHelper;
|
||||
|
||||
export type HelperRegistry<H extends BaseHelper = BaseHelper> = Map<
|
||||
@ -40,7 +42,7 @@ export type HelperRegistry<H extends BaseHelper = BaseHelper> = Map<
|
||||
Map<string, H>
|
||||
>;
|
||||
|
||||
export type HelperSetting<N extends string = string> = Omit<
|
||||
export type HelperSetting<N extends HelperName = HelperName> = Omit<
|
||||
SettingCreateDto,
|
||||
'group' | 'weight'
|
||||
> & {
|
||||
|
@ -8,14 +8,19 @@
|
||||
|
||||
import { Controller, Get, UseInterceptors } from '@nestjs/common';
|
||||
|
||||
import { ChannelService } from '@/channel/channel.service';
|
||||
import { HelperService } from '@/helper/helper.service';
|
||||
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||
|
||||
import { I18nService } from '../services/i18n.service';
|
||||
import { PluginService } from '@/plugins/plugins.service';
|
||||
|
||||
@UseInterceptors(CsrfInterceptor)
|
||||
@Controller('i18n')
|
||||
export class I18nController {
|
||||
constructor(private readonly i18nService: I18nService) {}
|
||||
constructor(
|
||||
private readonly pluginService: PluginService,
|
||||
private readonly helperService: HelperService,
|
||||
private readonly channelService: ChannelService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves translations of all the installed extensions.
|
||||
@ -23,6 +28,12 @@ export class I18nController {
|
||||
*/
|
||||
@Get()
|
||||
getTranslations() {
|
||||
return this.i18nService.getExtensionI18nTranslations();
|
||||
const plugins = this.pluginService.getAll();
|
||||
const helpers = this.helperService.getAll();
|
||||
const channels = this.channelService.getAll();
|
||||
return [...plugins, ...helpers, ...channels].reduce((acc, curr) => {
|
||||
acc[curr.getNamespace()] = curr.getTranslations();
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,8 @@
|
||||
* 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 { existsSync, promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
I18nJsonLoader,
|
||||
I18nTranslation,
|
||||
I18nService as NativeI18nService,
|
||||
Path,
|
||||
PathValue,
|
||||
@ -22,25 +17,13 @@ import { IfAnyOrNever } from 'nestjs-i18n/dist/types';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { Translation } from '@/i18n/schemas/translation.schema';
|
||||
import { hyphenToUnderscore } from '@/utils/helpers/misc';
|
||||
|
||||
@Injectable()
|
||||
export class I18nService<K = Record<string, unknown>>
|
||||
extends NativeI18nService<K>
|
||||
implements OnModuleInit
|
||||
{
|
||||
export class I18nService<
|
||||
K = Record<string, unknown>,
|
||||
> extends NativeI18nService<K> {
|
||||
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,
|
||||
@ -83,52 +66,4 @@ export class I18nService<K = Record<string, unknown>>
|
||||
return acc;
|
||||
}, this.dynamicTranslations);
|
||||
}
|
||||
|
||||
async loadExtensionI18nTranslations() {
|
||||
const baseDir = path.join(__dirname, '..', '..', 'extensions');
|
||||
const extensionTypes = ['channels', 'helpers', 'plugins'];
|
||||
|
||||
try {
|
||||
for (const type of extensionTypes) {
|
||||
const extensionsDir = path.join(baseDir, type);
|
||||
|
||||
if (!existsSync(extensionsDir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 namespace = hyphenToUnderscore(folder.name);
|
||||
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] = {
|
||||
[namespace]: translations[lang],
|
||||
};
|
||||
} else {
|
||||
this.extensionTranslations[lang][namespace] =
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
* 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 path from 'path';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Block, BlockFull } from '@/chat/schemas/block.schema';
|
||||
@ -17,6 +19,7 @@ import { PluginService } from './plugins.service';
|
||||
import {
|
||||
PluginBlockTemplate,
|
||||
PluginEffects,
|
||||
PluginName,
|
||||
PluginSetting,
|
||||
PluginType,
|
||||
} from './types';
|
||||
@ -29,16 +32,13 @@ export abstract class BaseBlockPlugin<
|
||||
|
||||
public readonly settings: T;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
settings: T,
|
||||
pluginService: PluginService<BasePlugin>,
|
||||
) {
|
||||
super(id, pluginService);
|
||||
this.settings = settings;
|
||||
constructor(name: PluginName, pluginService: PluginService<BasePlugin>) {
|
||||
super(name, pluginService);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
this.settings = require(path.join(this.getPath(), 'settings')).default;
|
||||
}
|
||||
|
||||
template: PluginBlockTemplate;
|
||||
abstract template: PluginBlockTemplate;
|
||||
|
||||
effects?: PluginEffects;
|
||||
|
||||
|
@ -10,13 +10,13 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { BasePlugin } from './base-plugin.service';
|
||||
import { PluginService } from './plugins.service';
|
||||
import { PluginType } from './types';
|
||||
import { PluginName, PluginType } from './types';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BaseEventPlugin extends BasePlugin {
|
||||
public readonly type: PluginType = PluginType.event;
|
||||
|
||||
constructor(id: string, pluginService: PluginService<BasePlugin>) {
|
||||
super(id, pluginService);
|
||||
constructor(name: PluginName, pluginService: PluginService<BasePlugin>) {
|
||||
super(name, pluginService);
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,24 @@
|
||||
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
import { Extension } from '@/utils/generics/extension';
|
||||
|
||||
import { PluginService } from './plugins.service';
|
||||
import { PluginType } from './types';
|
||||
import { PluginName, PluginType } from './types';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BasePlugin implements OnModuleInit {
|
||||
export abstract class BasePlugin extends Extension implements OnModuleInit {
|
||||
public readonly type: PluginType;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly name: PluginName,
|
||||
private pluginService: PluginService<BasePlugin>,
|
||||
) {}
|
||||
) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.pluginService.setPlugin(this.type, this.id, this);
|
||||
async onModuleInit() {
|
||||
await super.onModuleInit();
|
||||
this.pluginService.setPlugin(this.type, this.name, this);
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,14 @@ import { Attachment } from '@/attachment/schemas/attachment.schema';
|
||||
|
||||
import { BasePlugin } from './base-plugin.service';
|
||||
import { PluginService } from './plugins.service';
|
||||
import { PluginType } from './types';
|
||||
import { PluginName, PluginType } from './types';
|
||||
|
||||
@Injectable()
|
||||
export abstract class BaseStoragePlugin extends BasePlugin {
|
||||
public readonly type: PluginType = PluginType.storage;
|
||||
|
||||
constructor(id: string, pluginService: PluginService<BasePlugin>) {
|
||||
super(id, pluginService);
|
||||
constructor(name: PluginName, pluginService: PluginService<BasePlugin>) {
|
||||
super(name, pluginService);
|
||||
}
|
||||
|
||||
abstract fileExists(attachment: Attachment): Promise<boolean>;
|
||||
|
@ -20,7 +20,12 @@ import { ContentModel } from '@/cms/schemas/content.schema';
|
||||
|
||||
import { PluginService } from './plugins.service';
|
||||
|
||||
@InjectDynamicProviders('dist/extensions/**/*.plugin.js')
|
||||
@InjectDynamicProviders(
|
||||
// Core & under dev plugins
|
||||
'dist/extensions/**/*.plugin.js',
|
||||
// Installed plugins via npm
|
||||
'dist/.hexabot/plugins/**/*.plugin.js',
|
||||
)
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -23,7 +23,7 @@ describe('PluginsService', () => {
|
||||
imports: [LoggerModule],
|
||||
}).compile();
|
||||
pluginsService = module.get<PluginService>(PluginService);
|
||||
module.get<DummyPlugin>(DummyPlugin).onModuleInit();
|
||||
await module.get<DummyPlugin>(DummyPlugin).onModuleInit();
|
||||
});
|
||||
afterAll(async () => {
|
||||
jest.clearAllMocks();
|
||||
@ -37,7 +37,7 @@ describe('PluginsService', () => {
|
||||
|
||||
describe('getPlugin', () => {
|
||||
it('should return the required plugin', () => {
|
||||
const result = pluginsService.getPlugin(PluginType.block, 'dummy');
|
||||
const result = pluginsService.getPlugin(PluginType.block, 'dummy-plugin');
|
||||
expect(result).toBeInstanceOf(DummyPlugin);
|
||||
});
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { BasePlugin } from './base-plugin.service';
|
||||
import { PluginInstance } from './map-types';
|
||||
import { PluginType } from './types';
|
||||
import { PluginName, PluginType } from './types';
|
||||
|
||||
/**
|
||||
* @summary Service for managing and retrieving plugins.
|
||||
@ -38,18 +38,18 @@ export class PluginService<T extends BasePlugin = BasePlugin> {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Registers a plugin with a given key.
|
||||
* Registers a plugin with a given name.
|
||||
*
|
||||
* @param key The unique identifier for the plugin.
|
||||
* @param name The unique identifier for the plugin.
|
||||
* @param plugin The plugin instance to register.
|
||||
*/
|
||||
public setPlugin(type: PluginType, key: string, plugin: T) {
|
||||
public setPlugin(type: PluginType, name: PluginName, plugin: T) {
|
||||
const registry = this.registry.get(type);
|
||||
registry.set(key, plugin);
|
||||
registry.set(name, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered plugins as an array.
|
||||
* Retrieves all registered plugins by as an array.
|
||||
*
|
||||
* @returns An array containing all the registered plugins.
|
||||
*/
|
||||
@ -58,29 +58,39 @@ export class PluginService<T extends BasePlugin = BasePlugin> {
|
||||
return Array.from(registry.values()) as PluginInstance<PT>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered plugins as an array.
|
||||
*
|
||||
* @returns An array containing all the registered plugins.
|
||||
*/
|
||||
public getAll(): T[] {
|
||||
return Array.from(this.registry.values()) // Get all the inner maps
|
||||
.flatMap((innerMap) => Array.from(innerMap.values())); // Flatten and get the values from each inner map
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a plugin based on its key.
|
||||
*
|
||||
* @param id The key used to register the plugin.
|
||||
* @param name The key used to register the plugin.
|
||||
*
|
||||
* @returns The plugin associated with the given key, or `undefined` if not found.
|
||||
*/
|
||||
public getPlugin<PT extends PluginType>(type: PT, id: string) {
|
||||
public getPlugin<PT extends PluginType>(type: PT, name: PluginName) {
|
||||
const registry = this.registry.get(type);
|
||||
const plugin = registry.get(id);
|
||||
const plugin = registry.get(name);
|
||||
return plugin ? (plugin as PluginInstance<PT>) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a plugin by its internal `id` property.
|
||||
*
|
||||
* @param pluginId The unique `id` of the plugin to find.
|
||||
* @param name The unique `id` of the plugin to find.
|
||||
*
|
||||
* @returns The plugin with the matching `id`, or `undefined` if no plugin is found.
|
||||
*/
|
||||
public findPlugin<PT extends PluginType>(type: PT, pluginId: string) {
|
||||
public findPlugin<PT extends PluginType>(type: PT, name: PluginName) {
|
||||
return this.getAllByType(type).find((plugin) => {
|
||||
return plugin.id === pluginId;
|
||||
return plugin.name === name;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,14 @@
|
||||
* 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 { ChannelEvent } from '@/channel/lib/EventWrapper';
|
||||
import { BlockCreateDto } from '@/chat/dto/block.dto';
|
||||
import { Block } from '@/chat/schemas/block.schema';
|
||||
import { Conversation } from '@/chat/schemas/conversation.schema';
|
||||
import { SettingCreateDto } from '@/setting/dto/setting.dto';
|
||||
|
||||
export type PluginName = `${string}-plugin`;
|
||||
|
||||
export enum PluginType {
|
||||
event = 'event',
|
||||
block = 'block',
|
||||
@ -19,7 +22,6 @@ export enum PluginType {
|
||||
|
||||
export interface CustomBlocks {}
|
||||
|
||||
type ChannelEvent = any;
|
||||
type BlockAttrs = Partial<BlockCreateDto> & { name: string };
|
||||
|
||||
export type PluginSetting = Omit<SettingCreateDto, 'weight'>;
|
||||
|
@ -13,7 +13,7 @@ export const DEFAULT_SETTINGS = [
|
||||
{
|
||||
group: 'chatbot_settings',
|
||||
label: 'default_nlu_helper',
|
||||
value: 'core-nlu',
|
||||
value: 'core-nlu-helper',
|
||||
type: SettingType.select,
|
||||
config: {
|
||||
multiple: false,
|
||||
@ -27,7 +27,7 @@ export const DEFAULT_SETTINGS = [
|
||||
{
|
||||
group: 'chatbot_settings',
|
||||
label: 'default_llm_helper',
|
||||
value: 'ollama',
|
||||
value: 'ollama-helper',
|
||||
type: SettingType.select,
|
||||
config: {
|
||||
multiple: false,
|
||||
|
43
api/src/utils/generics/extension.ts
Normal file
43
api/src/utils/generics/extension.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { I18nJsonLoader, I18nTranslation } from 'nestjs-i18n';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { ExtensionName } from '../types/extension';
|
||||
|
||||
export abstract class Extension implements OnModuleInit {
|
||||
private translations: I18nTranslation | Observable<I18nTranslation>;
|
||||
|
||||
constructor(public readonly name: ExtensionName) {}
|
||||
|
||||
abstract getPath(): string;
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getNamespace<N extends ExtensionName = ExtensionName>() {
|
||||
return this.name.replaceAll('-', '_') as HyphenToUnderscore<N>;
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// Load i18n
|
||||
const i18nPath = path.join(this.getPath(), 'i18n');
|
||||
try {
|
||||
// Check if the i18n directory exists
|
||||
await fs.access(i18nPath);
|
||||
|
||||
// Load and merge translations
|
||||
const i18nLoader = new I18nJsonLoader({ path: i18nPath });
|
||||
this.translations = await i18nLoader.load();
|
||||
} catch (error) {
|
||||
// If the i18n folder does not exist or error in reading, skip this folder
|
||||
}
|
||||
}
|
||||
|
||||
getTranslations() {
|
||||
return this.translations;
|
||||
}
|
||||
}
|
@ -15,23 +15,27 @@ import {
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import { BaseBlockPlugin } from '@/plugins/base-block-plugin';
|
||||
import { PluginService } from '@/plugins/plugins.service';
|
||||
import { PluginSetting } from '@/plugins/types';
|
||||
import { PluginBlockTemplate, PluginSetting } from '@/plugins/types';
|
||||
|
||||
@Injectable()
|
||||
export class DummyPlugin extends BaseBlockPlugin<PluginSetting[]> {
|
||||
template: PluginBlockTemplate = { name: 'Dummy Plugin' };
|
||||
|
||||
constructor(
|
||||
pluginService: PluginService,
|
||||
private logger: LoggerService,
|
||||
) {
|
||||
super('dummy', [], pluginService);
|
||||
|
||||
this.template = { name: 'Dummy Plugin' };
|
||||
super('dummy-plugin', pluginService);
|
||||
|
||||
this.effects = {
|
||||
onStoreContextData: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
getPath(): string {
|
||||
return __dirname;
|
||||
}
|
||||
|
||||
async process() {
|
||||
const envelope: StdOutgoingTextEnvelope = {
|
||||
format: OutgoingMessageFormat.text,
|
||||
|
1
api/src/utils/test/dummy/settings.ts
Normal file
1
api/src/utils/test/dummy/settings.ts
Normal file
@ -0,0 +1 @@
|
||||
export default [];
|
10
api/src/utils/test/fixtures/conversation.ts
vendored
10
api/src/utils/test/fixtures/conversation.ts
vendored
@ -10,8 +10,8 @@ import mongoose from 'mongoose';
|
||||
|
||||
import { ConversationCreateDto } from '@/chat/dto/conversation.dto';
|
||||
import {
|
||||
ConversationModel,
|
||||
Conversation,
|
||||
ConversationModel,
|
||||
} from '@/chat/schemas/conversation.schema';
|
||||
|
||||
import { getFixturesWithDefaultValues } from '../defaultValues';
|
||||
@ -25,7 +25,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
sender: '0',
|
||||
active: true,
|
||||
context: {
|
||||
channel: 'messenger',
|
||||
channel: 'messenger-channel',
|
||||
text: 'Hi',
|
||||
payload: '',
|
||||
nlp: {
|
||||
@ -60,7 +60,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
foreign_id: '',
|
||||
labels: [],
|
||||
assignedTo: null,
|
||||
channel: { name: 'messenger' },
|
||||
channel: { name: 'messenger-channel' },
|
||||
},
|
||||
skip: {},
|
||||
attempt: 0,
|
||||
@ -71,7 +71,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
{
|
||||
sender: '1',
|
||||
context: {
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: 'Hello',
|
||||
payload: '',
|
||||
nlp: {
|
||||
@ -106,7 +106,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
foreign_id: '',
|
||||
labels: [],
|
||||
assignedTo: null,
|
||||
channel: { name: 'offline' },
|
||||
channel: { name: 'offline-channel' },
|
||||
},
|
||||
skip: {},
|
||||
attempt: 0,
|
||||
|
8
api/src/utils/test/fixtures/subscriber.ts
vendored
8
api/src/utils/test/fixtures/subscriber.ts
vendored
@ -27,7 +27,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'FR',
|
||||
channel: {
|
||||
name: 'messenger',
|
||||
name: 'messenger-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
@ -43,7 +43,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
@ -59,7 +59,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
@ -75,7 +75,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
|
@ -16,7 +16,7 @@ import { modelInstance } from './misc';
|
||||
import { subscriberInstance } from './subscriber';
|
||||
|
||||
export const contextBlankInstance: Context = {
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: '',
|
||||
payload: undefined,
|
||||
nlp: { entities: [] },
|
||||
@ -42,7 +42,7 @@ export const contextEmailVarInstance: Context = {
|
||||
};
|
||||
|
||||
export const contextGetStartedInstance: Context = {
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
text: 'Get Started',
|
||||
payload: 'GET_STARTED',
|
||||
nlp: { entities: [] },
|
||||
|
@ -25,7 +25,7 @@ export const subscriberInstance: Subscriber = {
|
||||
lastvisit: new Date(),
|
||||
retainedFrom: new Date(),
|
||||
channel: {
|
||||
name: 'offline',
|
||||
name: 'offline-channel',
|
||||
},
|
||||
labels: [],
|
||||
...modelInstance,
|
||||
|
5
api/src/utils/types/extension.ts
Normal file
5
api/src/utils/types/extension.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ChannelName } from '@/channel/types';
|
||||
import { HelperName } from '@/helper/types';
|
||||
import { PluginName } from '@/plugins/types';
|
||||
|
||||
export type ExtensionName = ChannelName | HelperName | PluginName;
|
@ -23,5 +23,11 @@
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.json", "test/**/*.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.json",
|
||||
"test/**/*.ts",
|
||||
"src/.hexabot/**/*.ts",
|
||||
"src/.hexabot/**/*.json"
|
||||
]
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export const ChatWidget = () => {
|
||||
<UiChatWidget
|
||||
config={{
|
||||
apiUrl,
|
||||
channel: "live-chat-tester",
|
||||
channel: "live-chat-tester-channel",
|
||||
token: "test",
|
||||
language: i18n.language,
|
||||
}}
|
||||
|
@ -31,7 +31,9 @@ export const CustomBlocks = () => {
|
||||
{customBlocks?.map((customBlock) => (
|
||||
<Block
|
||||
key={customBlock.id}
|
||||
title={t(`title.${customBlock.id}`, { ns: customBlock.id })}
|
||||
title={t(`title.${customBlock.namespace}`, {
|
||||
ns: customBlock.namespace,
|
||||
})}
|
||||
Icon={PluginIcon}
|
||||
blockTemplate={customBlock.template}
|
||||
name={customBlock.template.name}
|
||||
|
@ -63,7 +63,8 @@ const PluginMessageForm = () => {
|
||||
<SettingInput
|
||||
setting={setting}
|
||||
field={field}
|
||||
ns={message.plugin}
|
||||
// @TODO : clean this later
|
||||
ns={message.plugin.replaceAll("-", "_")}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
|
@ -22,14 +22,13 @@ export const useRemoteI18n = () => {
|
||||
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) => {
|
||||
Object.keys(additionalTranslations).forEach((namespace) => {
|
||||
Object.keys(additionalTranslations[namespace]).forEach((lang) => {
|
||||
i18n.addResourceBundle(
|
||||
lang,
|
||||
namespace,
|
||||
additionalTranslations[lang][namespace],
|
||||
additionalTranslations[namespace][lang],
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
@ -139,4 +139,6 @@ export interface ICustomBlockTemplateAttributes {
|
||||
// @TODO : templates doe not contain base schema attributes
|
||||
export interface ICustomBlockTemplate
|
||||
extends IBaseSchema,
|
||||
OmitPopulate<ICustomBlockTemplateAttributes, EntityType.CUSTOM_BLOCK> {}
|
||||
OmitPopulate<ICustomBlockTemplateAttributes, EntityType.CUSTOM_BLOCK> {
|
||||
namespace: string;
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ Once the widget is built, you can easily embed it into any webpage. Here's an ex
|
||||
ReactDOM.render(
|
||||
el(HexabotWidget, {
|
||||
apiUrl: 'https://api.yourdomain.com',
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
domContainer,
|
||||
@ -96,7 +96,7 @@ To prevent the website css from conflicting with the chat widget css, we can lev
|
||||
ReactDOM.render(
|
||||
React.createElement(HexabotWidget, {
|
||||
apiUrl: 'https://api.yourdomain.com',
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
shadowContainer,
|
||||
|
@ -34,7 +34,7 @@
|
||||
ReactDOM.render(
|
||||
React.createElement(HexabotWidget, {
|
||||
apiUrl: 'http://localhost:4000',
|
||||
channel: 'offline',
|
||||
channel: 'offline-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
shadowContainer,
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'live-chat-tester',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'live-chat-tester-channel',
|
||||
token: process.env.REACT_APP_WIDGET_TOKEN || 'test',
|
||||
language: 'en',
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<ChatWidget
|
||||
{...{
|
||||
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'offline',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'offline-channel',
|
||||
token: process.env.REACT_APP_WIDGET_TOKEN || 'token123',
|
||||
language: 'en',
|
||||
}}
|
||||
|
Loading…
Reference in New Issue
Block a user