diff --git a/api/package-lock.json b/api/package-lock.json index a0f96bcc..dea3b0cd 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -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": { diff --git a/api/package.json b/api/package.json index 6e133f86..ff505de8 100644 --- a/api/package.json +++ b/api/package.json @@ -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", diff --git a/api/src/channel/lib/EventWrapper.ts b/api/src/channel/lib/EventWrapper.ts index 722be1e7..64a8efc5 100644 --- a/api/src/channel/lib/EventWrapper.ts +++ b/api/src/channel/lib/EventWrapper.ts @@ -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, + S = SubscriberChannelDict[N], > { _adapter: A = {} as A; _handler: C; - channelAttrs: SubscriberChannelDict[ChannelNameOf]; + 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] = {}, - ) { + 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> { + getChannelData(): SubscriberChannelData { return { name: this._handler.getName(), ...this.channelAttrs, - } as SubscriberChannelData>; + } as SubscriberChannelData; } /** @@ -267,7 +267,8 @@ type GenericEventAdapter = { export class GenericEventWrapper extends EventWrapper< GenericEventAdapter, - GenericEvent + GenericEvent, + ChannelName > { /** * Constructor : channel's event wrapper diff --git a/api/src/channel/lib/Handler.ts b/api/src/channel/lib/Handler.ts index c23e58d9..fb277fdf 100644 --- a/api/src/channel/lib/Handler.ts +++ b/api/src/channel/lib/Handler.ts @@ -28,8 +28,6 @@ import { ChannelName, ChannelSetting } from '../types'; import EventWrapper from './EventWrapper'; -export type ChannelNameOf = C extends ChannelHandler ? 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, + event: EventWrapper, envelope: StdOutgoingEnvelope, options: any, context: any, @@ -189,7 +187,7 @@ export default abstract class ChannelHandler< */ abstract getUserData( - event: EventWrapper, + event: EventWrapper, ): Promise; /** diff --git a/api/src/chat/schemas/types/channel.ts b/api/src/chat/schemas/types/channel.ts index 4cab1fef..d53a3c60 100644 --- a/api/src/chat/schemas/types/channel.ts +++ b/api/src/chat/schemas/types/channel.ts @@ -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]; }; diff --git a/api/src/chat/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index b9615a0f..5dab34e6 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -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, actualConversation: Conversation, actualBlock: BlockFull, isFallback: boolean, @@ -267,7 +268,7 @@ describe('BlockService', () => { .mockImplementation( async ( actualConversation: ConversationFull, - event: WebEventWrapper, + event: WebEventWrapper, ) => { expect(actualConversation).toEqualPayload({ next: [], diff --git a/api/src/extensions/channels/hexabot-channel-messenger b/api/src/extensions/channels/hexabot-channel-messenger deleted file mode 160000 index cf7004ef..00000000 --- a/api/src/extensions/channels/hexabot-channel-messenger +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cf7004ef6adac1b5e033d06987f493a9b00e01d2 diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index eb428c20..b63a34f6 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -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(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, + event: WebEventWrapper, 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 { + async getUserData(event: WebEventWrapper): Promise { const sender = event.getSender(); const { id: _id, diff --git a/api/src/extensions/channels/web/wrapper.ts b/api/src/extensions/channels/web/wrapper.ts index b1650be7..b8c7cc7a 100644 --- a/api/src/extensions/channels/web/wrapper.ts +++ b/api/src/extensions/channels/web/wrapper.ts @@ -68,10 +68,12 @@ type WebEventAdapter = raw: Web.IncomingMessage; }; -export default class WebEventWrapper< - T extends - BaseWebChannelHandler = BaseWebChannelHandler, -> extends EventWrapper { +// eslint-disable-next-line prettier/prettier +export default class WebEventWrapper 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, event: Web.Event, channelAttrs: SubscriberChannelDict[typeof WEB_CHANNEL_NAME], ) { diff --git a/frontend/package.json b/frontend/package.json index e26ae7fe..a7846456 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 804ec95c..780bb87c 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -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" }, diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json index 1b85dbfc..efc23e5e 100644 --- a/frontend/public/locales/fr/translation.json +++ b/frontend/public/locales/fr/translation.json @@ -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" }, diff --git a/frontend/src/app-components/buttons/DropdownButton.tsx b/frontend/src/app-components/buttons/DropdownButton.tsx index 79dacb0f..cf205f76 100644 --- a/frontend/src/app-components/buttons/DropdownButton.tsx +++ b/frontend/src/app-components/buttons/DropdownButton.tsx @@ -32,6 +32,7 @@ interface AddPatternProps { label?: string; icon?: React.ReactNode; sx?: SxProps | undefined; + disabled?: boolean; } const DropdownButton: React.FC = ({ @@ -40,10 +41,13 @@ const DropdownButton: React.FC = ({ label = "Add", icon, sx, + disabled = false, }) => { const [anchorEl, setAnchorEl] = useState(null); const handleOpen = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); + if (!disabled) { + setAnchorEl(event.currentTarget); + } }; const handleClose = () => { setAnchorEl(null); @@ -61,6 +65,7 @@ const DropdownButton: React.FC = ({ onClick={handleOpen} startIcon={icon} endIcon={} + disabled={disabled} > {label} diff --git a/frontend/src/app-components/inputs/MultipleInput.tsx b/frontend/src/app-components/inputs/MultipleInput.tsx index 17c73a38..df2755b9 100644 --- a/frontend/src/app-components/inputs/MultipleInput.tsx +++ b/frontend/src/app-components/inputs/MultipleInput.tsx @@ -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( gap={1} mb={1} > - handleRemoveInput(input.id)} - disabled={inputs.length <= minInput} - > - - ( }} fullWidth /> + handleRemoveInput(input.id)} + disabled={inputs.length <= minInput} + > + + ))} { 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()); diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx index 4697b224..ece950b2 100644 --- a/frontend/src/components/settings/index.tsx +++ b/frontend/src/components/settings/index.tsx @@ -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); } +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 ( diff --git a/frontend/src/components/visual-editor/form/inputs/message/ButtonInput.tsx b/frontend/src/components/visual-editor/form/inputs/message/ButtonInput.tsx index 067c08a3..dae26a5b 100644 --- a/frontend/src/components/visual-editor/form/inputs/message/ButtonInput.tsx +++ b/frontend/src/components/visual-editor/form/inputs/message/ButtonInput.tsx @@ -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, @@ -45,22 +41,7 @@ const ButtonInput: FC = ({ 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 = ({ return ( <> - - { - setButtonType(e.target.value as ButtonType); - }} - > - {types.map((item) => ( - - {item.label} - - ))} - - - + = ({ } /> - + {button.type === ButtonType.postback ? ( = ({ /> )} - - {button.type === ButtonType.postback ? null : ( - { - const value = e.target.value; - - onChange({ - ...button, - messenger_extensions: e.target.value !== "none", - webview_height_ratio: - value !== "none" ? (value as WebviewHeightRatio) : undefined, - }); - }} - > - {t("label.none")} - {t("label.compact")} - {t("label.tall")} - {t("label.full")} - - )} - ); }; diff --git a/frontend/src/components/visual-editor/form/inputs/message/ButtonsInput.tsx b/frontend/src/components/visual-editor/form/inputs/message/ButtonsInput.tsx index 2a622afd..b1351aee 100644 --- a/frontend/src/components/visual-editor/form/inputs/message/ButtonsInput.tsx +++ b/frontend/src/components/visual-editor/form/inputs/message/ButtonsInput.tsx @@ -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 = ({ value, onChange, minInput = 1, - maxInput = 13, + maxInput = 3, disablePayload = false, fieldPath, }) => { @@ -40,11 +43,31 @@ const ButtonsInput: FC = ({ const [buttons, setButtons] = useState[]>( value.map((button) => createValueWithId(button)), ); - const addInput = () => { - setButtons([ - ...buttons, - createValueWithId({ type: ButtonType.postback, title: "", payload: "" }), - ]); + const actions: DropdownButtonAction[] = useMemo( + () => [ + { + icon: , + name: t("button.postback"), + defaultValue: { + type: ButtonType.postback, + title: "", + payload: "", + }, + }, + { + icon: , + 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 = ({ return ( + + {t("label.title")} + + + {t("label.payload")} / {t("label.url")} +   - - {t("label.type")} - - - {t("label.title")} - - - {t("label.payload")} / {t("label.url")} - - - {t("label.webview")} - {buttons.map(({ value, id }, idx) => ( - - removeInput(idx)} - disabled={buttons.length <= minInput} - > - - - = ({ onChange={updateInput(idx)} disablePayload={disablePayload} /> + + removeInput(idx)} + disabled={buttons.length <= minInput} + > + + + ))} - + /> ); }; diff --git a/frontend/src/components/visual-editor/form/inputs/message/QuickRepliesInput.tsx b/frontend/src/components/visual-editor/form/inputs/message/QuickRepliesInput.tsx index e80f737c..2db3bc10 100644 --- a/frontend/src/components/visual-editor/form/inputs/message/QuickRepliesInput.tsx +++ b/frontend/src/components/visual-editor/form/inputs/message/QuickRepliesInput.tsx @@ -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 = ({ const { t } = useTranslate(); const [quickReplies, setQuickReplies] = useState< ValueWithId[] - >(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 = ({ return ( - - -   + {quickReplies.length > 0 ? ( + + + {t("label.title")} + + + + {t("label.payload")} + + +   + + {quickReplies.map(({ value, id }, idx) => ( + + + + removeInput(idx)} + disabled={quickReplies.length <= minInput} + > + + + + + ))} - - {t("label.type")} - - - {t("label.title")} - - - - {t("label.payload")} - - {quickReplies.map(({ value, id }, idx) => ( - - - removeInput(idx)} - disabled={quickReplies.length <= minInput} - > - - - - - - ))} - + ) : ( + + {t("label.no_quick_replies")} + + )} ); diff --git a/frontend/src/components/visual-editor/form/inputs/message/QuickReplyInput.tsx b/frontend/src/components/visual-editor/form/inputs/message/QuickReplyInput.tsx index 87f89f7c..1e688024 100644 --- a/frontend/src/components/visual-editor/form/inputs/message/QuickReplyInput.tsx +++ b/frontend/src/components/visual-editor/form/inputs/message/QuickReplyInput.tsx @@ -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 = ({ idx, }) => { const { t } = useTranslate(); - const [quickReplyType, setQuickReplyType] = useState(value.content_type); + const [quickReplyType, _setQuickReplyType] = useState(value.content_type); const { register, formState: { errors }, } = useFormContext(); - const types = Object.values(QuickReplyType).map((value) => { - return { - value, - label: t(`label.${value}`), - }; - }); return ( <> - - { - 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) => ( - - {item.label} - - ))} - - - - + {quickReplyType !== QuickReplyType.location ? ( = ({ /> ) : null} - + {quickReplyType !== QuickReplyType.location ? ( { const { t } = useTranslate(); + const router = useRouter(); + const flowId = router.query.id?.toString(); const [model, setModel] = useState< DiagramModel | 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, diff --git a/frontend/src/pages/settings/groups/[group]/index.tsx b/frontend/src/pages/settings/groups/[group]/index.tsx new file mode 100644 index 00000000..94070960 --- /dev/null +++ b/frontend/src/pages/settings/groups/[group]/index.tsx @@ -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; diff --git a/frontend/src/pages/settings/groups/index.tsx b/frontend/src/pages/settings/groups/index.tsx new file mode 100644 index 00000000..4a656682 --- /dev/null +++ b/frontend/src/pages/settings/groups/index.tsx @@ -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; diff --git a/frontend/src/pages/settings.tsx b/frontend/src/pages/settings/index.tsx similarity index 100% rename from frontend/src/pages/settings.tsx rename to frontend/src/pages/settings/index.tsx diff --git a/frontend/src/pages/visual-editor/flows/[id]/index.tsx b/frontend/src/pages/visual-editor/flows/[id]/index.tsx new file mode 100644 index 00000000..168060fe --- /dev/null +++ b/frontend/src/pages/visual-editor/flows/[id]/index.tsx @@ -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; diff --git a/frontend/src/pages/visual-editor/flows/index.tsx b/frontend/src/pages/visual-editor/flows/index.tsx new file mode 100644 index 00000000..b9f909bd --- /dev/null +++ b/frontend/src/pages/visual-editor/flows/index.tsx @@ -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; diff --git a/frontend/src/pages/visual-editor.tsx b/frontend/src/pages/visual-editor/index.tsx similarity index 100% rename from frontend/src/pages/visual-editor.tsx rename to frontend/src/pages/visual-editor/index.tsx diff --git a/frontend/src/services/types.ts b/frontend/src/services/types.ts index 355a517f..56a0a659 100644 --- a/frontend/src/services/types.ts +++ b/frontend/src/services/types.ts @@ -65,6 +65,7 @@ export enum RouterType { RESET = "reset", VISUAL_EDITOR = "visual-editor", INBOX = "inbox", + SETTINGS = "settings", } export const FULL_WIDTH_PATHNAMES: TRouterValues[] = [ diff --git a/frontend/src/utils/laylout.ts b/frontend/src/utils/laylout.ts index 3d2dfa2e..32160d8b 100644 --- a/frontend/src/utils/laylout.ts +++ b/frontend/src/utils/laylout.ts @@ -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"; diff --git a/package-lock.json b/package-lock.json index b8e28d8c..1ffc5dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index ba89c5c7..7cda931d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/widget/package.json b/widget/package.json index f26ccc95..5f1130b3 100644 --- a/widget/package.json +++ b/widget/package.json @@ -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",