mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge branch 'main' into 440-issue-missing-dynamic-maxinput-prop-in-quick-replies-input-component
This commit is contained in:
commit
dce9421f44
4
api/package-lock.json
generated
4
api/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hexabot",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hexabot",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexabot",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"description": "Hexabot is a solution for creating and managing chatbots across multiple channels, leveraging AI for advanced conversational capabilities. It provides a user-friendly interface for building, training, and deploying chatbots with integrated support for various messaging platforms.",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only",
|
||||
|
||||
@ -20,7 +20,9 @@ import {
|
||||
import { Payload } from '@/chat/schemas/types/quick-reply';
|
||||
import { NLU } from '@/helper/types';
|
||||
|
||||
import ChannelHandler, { ChannelNameOf } from './Handler';
|
||||
import { ChannelName } from '../types';
|
||||
|
||||
import ChannelHandler from './Handler';
|
||||
|
||||
export interface ChannelEvent {}
|
||||
|
||||
@ -28,13 +30,15 @@ export interface ChannelEvent {}
|
||||
export default abstract class EventWrapper<
|
||||
A,
|
||||
E,
|
||||
C extends ChannelHandler = ChannelHandler,
|
||||
N extends ChannelName = ChannelName,
|
||||
C extends ChannelHandler = ChannelHandler<N>,
|
||||
S = SubscriberChannelDict[N],
|
||||
> {
|
||||
_adapter: A = {} as A;
|
||||
|
||||
_handler: C;
|
||||
|
||||
channelAttrs: SubscriberChannelDict[ChannelNameOf<C>];
|
||||
channelAttrs: S;
|
||||
|
||||
_profile!: Subscriber;
|
||||
|
||||
@ -50,11 +54,7 @@ export default abstract class EventWrapper<
|
||||
* @param event - The message event received
|
||||
* @param channelAttrs - Channel's specific data
|
||||
*/
|
||||
constructor(
|
||||
handler: C,
|
||||
event: E,
|
||||
channelAttrs: SubscriberChannelDict[ChannelNameOf<C>] = {},
|
||||
) {
|
||||
constructor(handler: C, event: E, channelAttrs: S = {} as S) {
|
||||
this._handler = handler;
|
||||
this._init(event);
|
||||
this.channelAttrs = channelAttrs;
|
||||
@ -106,11 +106,11 @@ export default abstract class EventWrapper<
|
||||
*
|
||||
* @returns Returns any channel related data.
|
||||
*/
|
||||
getChannelData(): SubscriberChannelData<ChannelNameOf<C>> {
|
||||
getChannelData(): SubscriberChannelData<N> {
|
||||
return {
|
||||
name: this._handler.getName(),
|
||||
...this.channelAttrs,
|
||||
} as SubscriberChannelData<ChannelNameOf<C>>;
|
||||
} as SubscriberChannelData<N>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,7 +267,8 @@ type GenericEventAdapter = {
|
||||
|
||||
export class GenericEventWrapper extends EventWrapper<
|
||||
GenericEventAdapter,
|
||||
GenericEvent
|
||||
GenericEvent,
|
||||
ChannelName
|
||||
> {
|
||||
/**
|
||||
* Constructor : channel's event wrapper
|
||||
|
||||
@ -28,8 +28,6 @@ import { ChannelName, ChannelSetting } from '../types';
|
||||
|
||||
import EventWrapper from './EventWrapper';
|
||||
|
||||
export type ChannelNameOf<C> = C extends ChannelHandler<infer N> ? N : never;
|
||||
|
||||
@Injectable()
|
||||
export default abstract class ChannelHandler<
|
||||
N extends ChannelName = ChannelName,
|
||||
@ -176,7 +174,7 @@ export default abstract class ChannelHandler<
|
||||
|
||||
*/
|
||||
abstract sendMessage(
|
||||
event: EventWrapper<any, any>,
|
||||
event: EventWrapper<any, any, N>,
|
||||
envelope: StdOutgoingEnvelope,
|
||||
options: any,
|
||||
context: any,
|
||||
@ -189,7 +187,7 @@ export default abstract class ChannelHandler<
|
||||
|
||||
*/
|
||||
abstract getUserData(
|
||||
event: EventWrapper<any, any>,
|
||||
event: EventWrapper<any, any, N>,
|
||||
): Promise<SubscriberCreateDto>;
|
||||
|
||||
/**
|
||||
|
||||
@ -10,12 +10,12 @@ import { ChannelName } from '@/channel/types';
|
||||
|
||||
export type SubscriberChannelData<
|
||||
C extends ChannelName = null,
|
||||
K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C],
|
||||
// K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C],
|
||||
> = C extends null
|
||||
? { name: ChannelName }
|
||||
: {
|
||||
name: C;
|
||||
} & {
|
||||
// Channel's specific attributes
|
||||
[P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][K];
|
||||
[P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][P];
|
||||
};
|
||||
|
||||
@ -26,6 +26,7 @@ import { ContentService } from '@/cms/services/content.service';
|
||||
import { MenuService } from '@/cms/services/menu.service';
|
||||
import { webEventText } from '@/extensions/channels/web/__test__/events.mock';
|
||||
import WebChannelHandler from '@/extensions/channels/web/index.channel';
|
||||
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
|
||||
import WebEventWrapper from '@/extensions/channels/web/wrapper';
|
||||
import { HelperService } from '@/helper/helper.service';
|
||||
import { LanguageRepository } from '@/i18n/repositories/language.repository';
|
||||
@ -201,7 +202,7 @@ describe('BlockService', () => {
|
||||
.spyOn(botService, 'findBlockAndSendReply')
|
||||
.mockImplementation(
|
||||
(
|
||||
actualEvent: WebEventWrapper,
|
||||
actualEvent: WebEventWrapper<typeof WEB_CHANNEL_NAME>,
|
||||
actualConversation: Conversation,
|
||||
actualBlock: BlockFull,
|
||||
isFallback: boolean,
|
||||
@ -267,7 +268,7 @@ describe('BlockService', () => {
|
||||
.mockImplementation(
|
||||
async (
|
||||
actualConversation: ConversationFull,
|
||||
event: WebEventWrapper,
|
||||
event: WebEventWrapper<typeof WEB_CHANNEL_NAME>,
|
||||
) => {
|
||||
expect(actualConversation).toEqualPayload({
|
||||
next: [],
|
||||
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit cf7004ef6adac1b5e033d06987f493a9b00e01d2
|
||||
@ -20,7 +20,6 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { Attachment } from '@/attachment/schemas/attachment.schema';
|
||||
import { AttachmentService } from '@/attachment/services/attachment.service';
|
||||
import { ChannelService } from '@/channel/channel.service';
|
||||
import EventWrapper from '@/channel/lib/EventWrapper';
|
||||
import ChannelHandler from '@/channel/lib/Handler';
|
||||
import { ChannelName } from '@/channel/types';
|
||||
import { MessageCreateDto } from '@/chat/dto/message.dto';
|
||||
@ -782,11 +781,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
data.data = upload;
|
||||
}
|
||||
const channelAttrs = this.getChannelAttributes(req);
|
||||
const event: WebEventWrapper = new WebEventWrapper(
|
||||
this,
|
||||
data,
|
||||
channelAttrs,
|
||||
);
|
||||
const event = new WebEventWrapper<N>(this, data, channelAttrs);
|
||||
if (event.getEventType() === 'message') {
|
||||
// Handler sync message sent by chabbot
|
||||
if (data.sync && data.author === 'chatbot') {
|
||||
@ -1206,7 +1201,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
* @returns The web's response, otherwise an error
|
||||
*/
|
||||
async sendMessage(
|
||||
event: EventWrapper<any, any>,
|
||||
event: WebEventWrapper<N>,
|
||||
envelope: StdOutgoingEnvelope,
|
||||
options: BlockOptions,
|
||||
_context?: any,
|
||||
@ -1280,7 +1275,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @returns The web's response, otherwise an error
|
||||
*/
|
||||
async getUserData(event: WebEventWrapper): Promise<SubscriberCreateDto> {
|
||||
async getUserData(event: WebEventWrapper<N>): Promise<SubscriberCreateDto> {
|
||||
const sender = event.getSender();
|
||||
const {
|
||||
id: _id,
|
||||
|
||||
@ -68,10 +68,12 @@ type WebEventAdapter =
|
||||
raw: Web.IncomingMessage<Web.IncomingAttachmentMessage>;
|
||||
};
|
||||
|
||||
export default class WebEventWrapper<
|
||||
T extends
|
||||
BaseWebChannelHandler<ChannelName> = BaseWebChannelHandler<ChannelName>,
|
||||
> extends EventWrapper<WebEventAdapter, Web.Event> {
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
export default class WebEventWrapper<N extends ChannelName> extends EventWrapper<
|
||||
WebEventAdapter,
|
||||
Web.Event,
|
||||
N
|
||||
> {
|
||||
/**
|
||||
* Constructor : channel's event wrapper
|
||||
*
|
||||
@ -80,7 +82,7 @@ export default class WebEventWrapper<
|
||||
* @param channelAttrs - Channel's specific extra attributes {isSocket, ipAddress}
|
||||
*/
|
||||
constructor(
|
||||
handler: T,
|
||||
handler: BaseWebChannelHandler<N>,
|
||||
event: Web.Event,
|
||||
channelAttrs: SubscriberChannelDict[typeof WEB_CHANNEL_NAME],
|
||||
) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "hexabot-ui",
|
||||
"private": true,
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"description": "Hexabot is a solution for creating and managing chatbots across multiple channels, leveraging AI for advanced conversational capabilities. It provides a user-friendly interface for building, training, and deploying chatbots with integrated support for various messaging platforms.",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only",
|
||||
|
||||
@ -233,6 +233,7 @@
|
||||
"block_event_type": "Type of event",
|
||||
"patterns": "Patterns",
|
||||
"no_patterns": "- No triggers -",
|
||||
"no_quick_replies": "- No quick replies -",
|
||||
"text_patterns": " Text Patterns",
|
||||
"triggers": "Triggers",
|
||||
"payloads": "Payloads",
|
||||
@ -558,6 +559,12 @@
|
||||
"manage_roles": "Manage Roles",
|
||||
"connect_with_sso": "Connect with SSO",
|
||||
"add_pattern": "New Trigger",
|
||||
"postback": "Postback",
|
||||
"url": "Url",
|
||||
"add_button": "New Button",
|
||||
"add_quick_reply": "New Quick Reply",
|
||||
"text": "Text",
|
||||
"location": "Location",
|
||||
"mark_as_default": "Mark as Default",
|
||||
"toggle": "Toggle button"
|
||||
},
|
||||
|
||||
@ -234,6 +234,7 @@
|
||||
"patterns": "Motifs",
|
||||
"text_patterns": "Motifs textuels",
|
||||
"no_patterns": "- Aucun motif -",
|
||||
"no_quick_replies": "- Aucune réponse rapide -",
|
||||
"triggers": "Déclencheurs",
|
||||
"payloads": "Payloads",
|
||||
"general_payloads": "Payloads généraux",
|
||||
@ -559,6 +560,12 @@
|
||||
"manage_roles": "Gérer les rôles",
|
||||
"connect_with_sso": "Se connecter avec SSO",
|
||||
"add_pattern": "Ajouter un déclencheur",
|
||||
"postback": "Valeur de retour",
|
||||
"url": "Url",
|
||||
"add_button": "Ajouter un bouton",
|
||||
"add_quick_reply": "Ajouter une réponse rapide",
|
||||
"text": "Texte",
|
||||
"location": "Emplacement",
|
||||
"mark_as_default": "Par Défaut",
|
||||
"toggle": "Bouton de bascule"
|
||||
},
|
||||
|
||||
@ -32,6 +32,7 @@ interface AddPatternProps {
|
||||
label?: string;
|
||||
icon?: React.ReactNode;
|
||||
sx?: SxProps<Theme> | undefined;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const DropdownButton: React.FC<AddPatternProps> = ({
|
||||
@ -40,10 +41,13 @@ const DropdownButton: React.FC<AddPatternProps> = ({
|
||||
label = "Add",
|
||||
icon,
|
||||
sx,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
if (!disabled) {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
@ -61,6 +65,7 @@ const DropdownButton: React.FC<AddPatternProps> = ({
|
||||
onClick={handleOpen}
|
||||
startIcon={icon}
|
||||
endIcon={<ArrowDropDown />}
|
||||
disabled={disabled}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { RemoveCircleOutline } from "@mui/icons-material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -130,12 +130,6 @@ const MultipleInput = forwardRef<HTMLDivElement, MultipleInputProps>(
|
||||
gap={1}
|
||||
mb={1}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => handleRemoveInput(input.id)}
|
||||
disabled={inputs.length <= minInput}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<Input
|
||||
{...(getInputProps ? getInputProps(idx) : null)}
|
||||
{...rest}
|
||||
@ -146,6 +140,13 @@ const MultipleInput = forwardRef<HTMLDivElement, MultipleInputProps>(
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => handleRemoveInput(input.id)}
|
||||
disabled={inputs.length <= minInput}
|
||||
>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
</Box>
|
||||
))}
|
||||
<Box
|
||||
|
||||
@ -27,7 +27,7 @@ export const ChatWidget = () => {
|
||||
const pathname = usePathname();
|
||||
const { apiUrl } = useConfig();
|
||||
const { isAuthenticated } = useAuth();
|
||||
const isVisualEditor = pathname === `/${RouterType.VISUAL_EDITOR}`;
|
||||
const isVisualEditor = pathname.startsWith(`/${RouterType.VISUAL_EDITOR}`);
|
||||
const allowedDomainsSetting = useSetting(SETTING_TYPE, "allowed_domains");
|
||||
const themeColorSetting = useSetting(SETTING_TYPE, "theme_color");
|
||||
const [key, setKey] = useState(generateId());
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
Tab,
|
||||
Tabs,
|
||||
} from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
@ -25,7 +26,7 @@ import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||
import { useToast } from "@/hooks/useToast";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { PageHeader } from "@/layout/content/PageHeader";
|
||||
import { EntityType } from "@/services/types";
|
||||
import { EntityType, RouterType } from "@/services/types";
|
||||
import { ISetting } from "@/types/setting.types";
|
||||
import { SXStyleOptions } from "@/utils/SXStyleOptions";
|
||||
|
||||
@ -66,10 +67,16 @@ function groupBy(array: ISetting[]) {
|
||||
}, {} as Record<string, ISetting[]>);
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS_GROUP = "chatbot_settings" as const;
|
||||
|
||||
export const Settings = () => {
|
||||
const { t } = useTranslate();
|
||||
const router = useRouter();
|
||||
const group = router.query.group?.toString();
|
||||
const { toast } = useToast();
|
||||
const [selectedTab, setSelectedTab] = useState("chatbot_settings");
|
||||
const [selectedTab, setSelectedTab] = useState(
|
||||
group || DEFAULT_SETTINGS_GROUP,
|
||||
);
|
||||
const { control, watch } = useForm();
|
||||
const { data: settings } = useFind(
|
||||
{ entity: EntityType.SETTING },
|
||||
@ -94,6 +101,7 @@ export const Settings = () => {
|
||||
};
|
||||
const handleChange = (_event: React.SyntheticEvent, newValue: string) => {
|
||||
setSelectedTab(newValue);
|
||||
router.push(`/${RouterType.SETTINGS}/groups/${newValue}`);
|
||||
};
|
||||
const isDisabled = (setting: ISetting) => {
|
||||
return (
|
||||
@ -135,6 +143,12 @@ export const Settings = () => {
|
||||
};
|
||||
}, [watch, debouncedUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTab(group || DEFAULT_SETTINGS_GROUP);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [group]);
|
||||
|
||||
return (
|
||||
<Grid container gap={3} flexDirection="column">
|
||||
<PageHeader icon={faCogs} title={t("title.settings")} />
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { Grid, MenuItem } from "@mui/material";
|
||||
import { Grid } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { FieldPath, useFormContext } from "react-hook-form";
|
||||
|
||||
@ -15,11 +15,7 @@ import { ToggleableInput } from "@/app-components/inputs/ToggleableInput";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { useValidationRules } from "@/hooks/useValidationRules";
|
||||
import { IBlockAttributes } from "@/types/block.types";
|
||||
import {
|
||||
AnyButton,
|
||||
ButtonType,
|
||||
WebviewHeightRatio,
|
||||
} from "@/types/message.types";
|
||||
import { AnyButton, ButtonType } from "@/types/message.types";
|
||||
|
||||
const buildFieldPath = (
|
||||
fieldPath: FieldPath<IBlockAttributes>,
|
||||
@ -45,22 +41,7 @@ const ButtonInput: FC<ButtonInputProps> = ({
|
||||
fieldPath,
|
||||
}) => {
|
||||
const { t } = useTranslate();
|
||||
const types: { value: ButtonType; label: string }[] = [
|
||||
{ value: ButtonType.postback, label: t("label.postback") },
|
||||
{ value: ButtonType.web_url, label: t("label.web_url") },
|
||||
];
|
||||
const rules = useValidationRules();
|
||||
const setButtonType = (type: ButtonType) => {
|
||||
if (type === ButtonType.postback) {
|
||||
onChange({
|
||||
type: ButtonType.postback,
|
||||
title: button.title,
|
||||
payload: button.title,
|
||||
});
|
||||
} else {
|
||||
onChange({ type: ButtonType.web_url, title: button.title, url: "" });
|
||||
}
|
||||
};
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
@ -68,22 +49,7 @@ const ButtonInput: FC<ButtonInputProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={2}>
|
||||
<Input
|
||||
select
|
||||
value={button.type}
|
||||
onChange={(e) => {
|
||||
setButtonType(e.target.value as ButtonType);
|
||||
}}
|
||||
>
|
||||
{types.map((item) => (
|
||||
<MenuItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Input>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid item xs={5}>
|
||||
<Input
|
||||
fullWidth
|
||||
required
|
||||
@ -107,7 +73,7 @@ const ButtonInput: FC<ButtonInputProps> = ({
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={6}>
|
||||
{button.type === ButtonType.postback ? (
|
||||
<ToggleableInput
|
||||
defaultValue={button.payload}
|
||||
@ -163,29 +129,6 @@ const ButtonInput: FC<ButtonInputProps> = ({
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
{button.type === ButtonType.postback ? null : (
|
||||
<Input
|
||||
select
|
||||
value={button.webview_height_ratio || "none"}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
onChange({
|
||||
...button,
|
||||
messenger_extensions: e.target.value !== "none",
|
||||
webview_height_ratio:
|
||||
value !== "none" ? (value as WebviewHeightRatio) : undefined,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MenuItem value="none">{t("label.none")}</MenuItem>
|
||||
<MenuItem value="compact">{t("label.compact")}</MenuItem>
|
||||
<MenuItem value="tall">{t("label.tall")}</MenuItem>
|
||||
<MenuItem value="full">{t("label.full")}</MenuItem>
|
||||
</Input>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,12 +6,15 @@
|
||||
* 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 { KeyboardReturn, Link, RemoveCircleOutline } from "@mui/icons-material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { Box, Button, Grid, IconButton } from "@mui/material";
|
||||
import { FC, Fragment, useEffect, useState } from "react";
|
||||
import { Box, Grid, IconButton } from "@mui/material";
|
||||
import { FC, Fragment, useEffect, useMemo, useState } from "react";
|
||||
import { FieldPath } from "react-hook-form";
|
||||
|
||||
import DropdownButton, {
|
||||
DropdownButtonAction,
|
||||
} from "@/app-components/buttons/DropdownButton";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { IBlockAttributes } from "@/types/block.types";
|
||||
import { AnyButton, ButtonType } from "@/types/message.types";
|
||||
@ -32,7 +35,7 @@ const ButtonsInput: FC<ButtonsInput> = ({
|
||||
value,
|
||||
onChange,
|
||||
minInput = 1,
|
||||
maxInput = 13,
|
||||
maxInput = 3,
|
||||
disablePayload = false,
|
||||
fieldPath,
|
||||
}) => {
|
||||
@ -40,11 +43,31 @@ const ButtonsInput: FC<ButtonsInput> = ({
|
||||
const [buttons, setButtons] = useState<ValueWithId<AnyButton>[]>(
|
||||
value.map((button) => createValueWithId(button)),
|
||||
);
|
||||
const addInput = () => {
|
||||
setButtons([
|
||||
...buttons,
|
||||
createValueWithId({ type: ButtonType.postback, title: "", payload: "" }),
|
||||
]);
|
||||
const actions: DropdownButtonAction[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: <KeyboardReturn />,
|
||||
name: t("button.postback"),
|
||||
defaultValue: {
|
||||
type: ButtonType.postback,
|
||||
title: "",
|
||||
payload: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <Link />,
|
||||
name: t("button.url"),
|
||||
defaultValue: {
|
||||
type: ButtonType.web_url,
|
||||
title: "",
|
||||
url: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
[t],
|
||||
);
|
||||
const addInput = (defaultValue: AnyButton) => {
|
||||
setButtons([...buttons, createValueWithId(defaultValue)]);
|
||||
};
|
||||
const removeInput = (index: number) => {
|
||||
const updatedButtons = [...buttons];
|
||||
@ -75,31 +98,17 @@ const ButtonsInput: FC<ButtonsInput> = ({
|
||||
return (
|
||||
<Box>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={5}>
|
||||
{t("label.title")}
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
{t("label.payload")} / {t("label.url")}
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
{t("label.type")}
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
{t("label.title")}
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
{t("label.payload")} / {t("label.url")}
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
{t("label.webview")}
|
||||
</Grid>
|
||||
{buttons.map(({ value, id }, idx) => (
|
||||
<Fragment key={id}>
|
||||
<Grid item xs={1}>
|
||||
<IconButton
|
||||
onClick={() => removeInput(idx)}
|
||||
disabled={buttons.length <= minInput}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<ButtonInput
|
||||
fieldPath={fieldPath}
|
||||
idx={idx}
|
||||
@ -107,19 +116,26 @@ const ButtonsInput: FC<ButtonsInput> = ({
|
||||
onChange={updateInput(idx)}
|
||||
disablePayload={disablePayload}
|
||||
/>
|
||||
<Grid item xs={1}>
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => removeInput(idx)}
|
||||
disabled={buttons.length <= minInput}
|
||||
>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={addInput}
|
||||
startIcon={<AddIcon />}
|
||||
sx={{ m: 1, float: "right" }}
|
||||
<DropdownButton
|
||||
sx={{ m: 1, float: "right", padding: "16px" }}
|
||||
label={t("button.add_button")}
|
||||
actions={actions}
|
||||
onClick={(action) => addInput(action.defaultValue)}
|
||||
icon={<AddIcon />}
|
||||
disabled={buttons.length >= maxInput}
|
||||
>
|
||||
{t("button.add")}
|
||||
</Button>
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { RemoveCircleOutline } from "@mui/icons-material";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { Box, Button, Grid, IconButton } from "@mui/material";
|
||||
import { Box, Button, Grid, IconButton, Typography } from "@mui/material";
|
||||
import { FC, Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
@ -33,8 +33,8 @@ const QuickRepliesInput: FC<QuickRepliesInput> = ({
|
||||
const { t } = useTranslate();
|
||||
const [quickReplies, setQuickReplies] = useState<
|
||||
ValueWithId<StdQuickReply>[]
|
||||
>(value.map((quickReplie) => createValueWithId(quickReplie)));
|
||||
const addInput = () => {
|
||||
>(value.map((quickReply) => createValueWithId(quickReply)));
|
||||
const addInput = (): void => {
|
||||
setQuickReplies([
|
||||
...quickReplies,
|
||||
createValueWithId({
|
||||
@ -72,48 +72,61 @@ const QuickRepliesInput: FC<QuickRepliesInput> = ({
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={1}>
|
||||
|
||||
{quickReplies.length > 0 ? (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={5}>
|
||||
{t("label.title")}
|
||||
</Grid>
|
||||
<Grid item xs={1} />
|
||||
<Grid item xs={5}>
|
||||
{t("label.payload")}
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
|
||||
</Grid>
|
||||
{quickReplies.map(({ value, id }, idx) => (
|
||||
<Fragment key={id}>
|
||||
<QuickReplyInput
|
||||
value={value}
|
||||
idx={idx}
|
||||
onChange={updateInput(idx)}
|
||||
/>
|
||||
<Grid item xs={1}>
|
||||
<IconButton
|
||||
color="error"
|
||||
size="medium"
|
||||
onClick={() => removeInput(idx)}
|
||||
disabled={quickReplies.length <= minInput}
|
||||
>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
{t("label.type")}
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
{t("label.title")}
|
||||
</Grid>
|
||||
<Grid item xs={1} />
|
||||
<Grid item xs={4}>
|
||||
{t("label.payload")}
|
||||
</Grid>
|
||||
{quickReplies.map(({ value, id }, idx) => (
|
||||
<Fragment key={id}>
|
||||
<Grid item xs={1}>
|
||||
<IconButton
|
||||
size="medium"
|
||||
onClick={() => removeInput(idx)}
|
||||
disabled={quickReplies.length <= minInput}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<QuickReplyInput
|
||||
value={value}
|
||||
idx={idx}
|
||||
onChange={updateInput(idx)}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "lightgrey",
|
||||
textAlign: "center",
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
{t("label.no_quick_replies")}
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
float: "right",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={addInput}
|
||||
startIcon={<AddIcon />}
|
||||
sx={{ marginTop: 2, float: "right" }}
|
||||
onClick={addInput}
|
||||
disabled={quickReplies.length >= maxInput}
|
||||
>
|
||||
{t("button.add")}
|
||||
{t("button.add_quick_reply")}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { Grid, MenuItem } from "@mui/material";
|
||||
import { Grid } from "@mui/material";
|
||||
import { FC, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
@ -28,51 +28,15 @@ const QuickReplyInput: FC<QuickReplyInputProps> = ({
|
||||
idx,
|
||||
}) => {
|
||||
const { t } = useTranslate();
|
||||
const [quickReplyType, setQuickReplyType] = useState(value.content_type);
|
||||
const [quickReplyType, _setQuickReplyType] = useState(value.content_type);
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext<IBlockAttributes>();
|
||||
const types = Object.values(QuickReplyType).map((value) => {
|
||||
return {
|
||||
value,
|
||||
label: t(`label.${value}`),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={2}>
|
||||
<Input
|
||||
select
|
||||
defaultValue={quickReplyType}
|
||||
onChange={(e) => {
|
||||
const selected = e.target.value as QuickReplyType;
|
||||
|
||||
setQuickReplyType(selected);
|
||||
|
||||
onChange(
|
||||
selected === QuickReplyType.location
|
||||
? {
|
||||
content_type: QuickReplyType.location,
|
||||
}
|
||||
: {
|
||||
content_type: selected,
|
||||
title: "",
|
||||
payload: "",
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
{types.map((item) => (
|
||||
<MenuItem key={item.value.toString()} value={item.value.toString()}>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Input>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={4}>
|
||||
<Grid item xs={5}>
|
||||
{quickReplyType !== QuickReplyType.location ? (
|
||||
<Input
|
||||
value={value.title}
|
||||
@ -99,7 +63,7 @@ const QuickReplyInput: FC<QuickReplyInputProps> = ({
|
||||
/>
|
||||
) : null}
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<Grid item xs={6}>
|
||||
{quickReplyType !== QuickReplyType.location ? (
|
||||
<ToggleableInput
|
||||
defaultValue={value.payload}
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
DiagramModel,
|
||||
DiagramModelGenerics,
|
||||
} from "@projectstorm/react-diagrams";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
SyntheticEvent,
|
||||
useCallback,
|
||||
@ -52,7 +53,7 @@ import useDebouncedUpdate from "@/hooks/useDebouncedUpdate";
|
||||
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||
import { useSearch } from "@/hooks/useSearch";
|
||||
import { useTranslate } from "@/hooks/useTranslate";
|
||||
import { EntityType, Format, QueryType } from "@/services/types";
|
||||
import { EntityType, Format, QueryType, RouterType } from "@/services/types";
|
||||
import { IBlock } from "@/types/block.types";
|
||||
import { ICategory } from "@/types/category.types";
|
||||
import { BlockPorts } from "@/types/visual-editor.types";
|
||||
@ -65,6 +66,8 @@ import { AdvancedLinkModel } from "./AdvancedLink/AdvancedLinkModel";
|
||||
|
||||
const Diagrams = () => {
|
||||
const { t } = useTranslate();
|
||||
const router = useRouter();
|
||||
const flowId = router.query.id?.toString();
|
||||
const [model, setModel] = useState<
|
||||
DiagramModel<DiagramModelGenerics> | undefined
|
||||
>();
|
||||
@ -95,7 +98,9 @@ const Diagrams = () => {
|
||||
},
|
||||
{
|
||||
onSuccess([{ id, zoom, offset }]) {
|
||||
if (id) {
|
||||
if (flowId) {
|
||||
setSelectedCategoryId?.(flowId);
|
||||
} else if (id) {
|
||||
setSelectedCategoryId?.(id);
|
||||
if (engine?.getModel()) {
|
||||
setViewerOffset(offset || [0, 0]);
|
||||
@ -161,6 +166,8 @@ const Diagrams = () => {
|
||||
if (id) {
|
||||
setSelectedCategoryId?.(id);
|
||||
setSelectedBlockId(undefined); // Reset selected block when switching categories, resetting edit & remove buttons
|
||||
|
||||
router.push(`/${RouterType.VISUAL_EDITOR}/flows/${id}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -181,6 +188,12 @@ const Diagrams = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategoryId(flowId || "");
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [flowId]);
|
||||
|
||||
useEffect(() => {
|
||||
const { canvas, model, engine } = buildDiagram({
|
||||
zoom: currentCategory?.zoom || 100,
|
||||
|
||||
11
frontend/src/pages/settings/groups/[group]/index.tsx
Normal file
11
frontend/src/pages/settings/groups/[group]/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import SettingsPage from "../..";
|
||||
|
||||
export default SettingsPage;
|
||||
11
frontend/src/pages/settings/groups/index.tsx
Normal file
11
frontend/src/pages/settings/groups/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import SettingsPage from "..";
|
||||
|
||||
export default SettingsPage;
|
||||
11
frontend/src/pages/visual-editor/flows/[id]/index.tsx
Normal file
11
frontend/src/pages/visual-editor/flows/[id]/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import VisualEditorPage from "../..";
|
||||
|
||||
export default VisualEditorPage;
|
||||
11
frontend/src/pages/visual-editor/flows/index.tsx
Normal file
11
frontend/src/pages/visual-editor/flows/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import VisualEditorPage from "..";
|
||||
|
||||
export default VisualEditorPage;
|
||||
@ -65,6 +65,7 @@ export enum RouterType {
|
||||
RESET = "reset",
|
||||
VISUAL_EDITOR = "visual-editor",
|
||||
INBOX = "inbox",
|
||||
SETTINGS = "settings",
|
||||
}
|
||||
|
||||
export const FULL_WIDTH_PATHNAMES: TRouterValues[] = [
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
* 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 { FULL_WIDTH_PATHNAMES, TRouterValues } from "@/services/types";
|
||||
import { FULL_WIDTH_PATHNAMES } from "@/services/types";
|
||||
|
||||
type TLayout = "default" | "full_width";
|
||||
|
||||
export const getLayout = (pathname: string): TLayout =>
|
||||
FULL_WIDTH_PATHNAMES.includes(pathname as TRouterValues)
|
||||
FULL_WIDTH_PATHNAMES.some((path) => pathname.startsWith(path))
|
||||
? "full_width"
|
||||
: "default";
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "hexabot",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hexabot",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.5",
|
||||
"license": "AGPL-3.0-only",
|
||||
"workspaces": [
|
||||
"frontend",
|
||||
@ -45,7 +45,7 @@
|
||||
},
|
||||
"frontend": {
|
||||
"name": "hexabot-ui",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||
@ -9771,7 +9771,7 @@
|
||||
},
|
||||
"widget": {
|
||||
"name": "hexabot-chat-widget",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@types/emoji-js": "^3.5.2",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"frontend",
|
||||
"widget"
|
||||
],
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"description": "Hexabot is a solution for creating and managing chatbots across multiple channels, leveraging AI for advanced conversational capabilities. It provides a user-friendly interface for building, training, and deploying chatbots with integrated support for various messaging platforms.",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hexabot-chat-widget",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.6",
|
||||
"description": "Hexabot is a solution for creating and managing chatbots across multiple channels, leveraging AI for advanced conversational capabilities. It provides a user-friendly interface for building, training, and deploying chatbots with integrated support for various messaging platforms.",
|
||||
"author": "Hexastack",
|
||||
"license": "AGPL-3.0-only",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user