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