diff --git a/api/src/user/controllers/auth.controller.ts b/api/src/user/controllers/auth.controller.ts index 559bc2ee..d34f3e26 100644 --- a/api/src/user/controllers/auth.controller.ts +++ b/api/src/user/controllers/auth.controller.ts @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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. @@ -11,6 +11,7 @@ import { Body, Controller, Get, + Inject, InternalServerErrorException, Param, Post, @@ -21,6 +22,7 @@ import { UseGuards, UseInterceptors, } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; import { CsrfCheck, CsrfGen, CsrfGenAuth } from '@tekuconcept/nestjs-csrf'; import { Request, Response } from 'express'; import { Session as ExpressSession } from 'express-session'; @@ -38,6 +40,9 @@ import { UserService } from '../services/user.service'; import { ValidateAccountService } from '../services/validate-account.service'; export class BaseAuthController { + @Inject(EventEmitter2) + private readonly eventEmitter: EventEmitter2; + constructor(protected readonly logger: LoggerService) {} /** @@ -67,6 +72,7 @@ export class BaseAuthController { @Session() session: ExpressSession, @Res({ passthrough: true }) res: Response, ) { + this.eventEmitter.emit('hook:user:logout', session); res.clearCookie(config.session.name); session.destroy((error) => { diff --git a/api/src/websocket/websocket.gateway.ts b/api/src/websocket/websocket.gateway.ts index 10c7c628..f6e1b0f6 100644 --- a/api/src/websocket/websocket.gateway.ts +++ b/api/src/websocket/websocket.gateway.ts @@ -1,12 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 { EventEmitter2 } from '@nestjs/event-emitter'; +import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { ConnectedSocket, MessageBody, @@ -20,7 +20,7 @@ import { import cookie from 'cookie'; import * as cookieParser from 'cookie-parser'; import signature from 'cookie-signature'; -import { SessionData } from 'express-session'; +import { Session as ExpressSession, SessionData } from 'express-session'; import { Server, Socket } from 'socket.io'; import { sync as uid } from 'uid-safe'; @@ -258,6 +258,15 @@ export class WebsocketGateway this.eventEmitter.emit(`hook:websocket:connection`, client); } + @OnEvent('hook:user:logout') + disconnectSockets({ id }: ExpressSession) { + for (const [, socket] of this.io.sockets.sockets) { + if (socket.data['sessionID'] === id) { + socket.disconnect(true); + } + } + } + async handleDisconnect(client: Socket): Promise { this.logger.log(`Client id:${client.id} disconnected`); // Configurable custom afterDisconnect logic here diff --git a/api/types/event-emitter.d.ts b/api/types/event-emitter.d.ts index a39d15cf..4678d45b 100644 --- a/api/types/event-emitter.d.ts +++ b/api/types/event-emitter.d.ts @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 { type Session as ExpressSession } from 'express-session'; import type { Document, Query } from 'mongoose'; import { type Socket } from 'socket.io'; @@ -162,7 +163,7 @@ declare module '@nestjs/event-emitter' { model: TDefinition; permission: TDefinition; role: TDefinition; - user: TDefinition; + user: TDefinition; } /* entities hooks having schemas */ diff --git a/frontend/src/app-components/inputs/FileInput.tsx b/frontend/src/app-components/inputs/FileInput.tsx index f6d52d58..c6da510b 100644 --- a/frontend/src/app-components/inputs/FileInput.tsx +++ b/frontend/src/app-components/inputs/FileInput.tsx @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 UploadIcon from "@mui/icons-material/Upload"; import { Button, CircularProgress } from "@mui/material"; import { ChangeEvent, forwardRef } from "react"; @@ -71,6 +72,7 @@ const FileUploadButton = forwardRef( value="" // to trigger an automatic reset to allow the same file to be selected multiple times sx={{ display: "none" }} onChange={handleImportChange} + inputProps={{ accept }} /> ); diff --git a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx index 79f63582..969c03b8 100644 --- a/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx +++ b/frontend/src/components/visual-editor/v2/AdvancedLink/AdvancedLinkFactory.tsx @@ -1,18 +1,18 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 { css, keyframes } from "@emotion/react"; import styled from "@emotion/styled"; import { DefaultLinkFactory, DefaultLinkWidget, } from "@projectstorm/react-diagrams"; -import React from "react"; import { AdvancedLinkModel } from "./AdvancedLinkModel"; diff --git a/frontend/src/components/visual-editor/v2/BackgroundLayerWidget.tsx b/frontend/src/components/visual-editor/v2/BackgroundLayerWidget.tsx new file mode 100644 index 00000000..c8c3ae27 --- /dev/null +++ b/frontend/src/components/visual-editor/v2/BackgroundLayerWidget.tsx @@ -0,0 +1,62 @@ +/* + * Copyright © 2025 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 { css } from "@emotion/react"; +import styled from "@emotion/styled"; +import { CanvasModel } from "@projectstorm/react-canvas-core"; +import * as React from "react"; +import { CSSProperties } from "react"; + +export interface BackgroundLayerWidgetProps { + model: CanvasModel; +} + +namespace S { + const shared = css` + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + pointer-events: none; + transform-origin: 0 0; + width: 200%; + height: 200%; + overflow: visible; + background-color: #f2f4f7; + background-image: radial-gradient( + circle, + rgba(0, 0, 0, 0.075) 8%, + transparent 10% + ); + background-size: 16px 16px; + `; + + export const DivLayer = styled.div` + ${shared} + `; +} + +export class BackgroundLayerWidget extends React.Component< + React.PropsWithChildren +> { + constructor(props: BackgroundLayerWidgetProps) { + super(props); + this.state = {}; + } + + getTransformStyle(): CSSProperties { + return { + backgroundPosition: `${this.props.model.getOffsetX()}px ${this.props.model.getOffsetY()}px`, + }; + } + + render() { + return ; + } +} diff --git a/frontend/src/components/visual-editor/v2/CustomCanvasWidget.tsx b/frontend/src/components/visual-editor/v2/CustomCanvasWidget.tsx index eb356c78..5435932a 100644 --- a/frontend/src/components/visual-editor/v2/CustomCanvasWidget.tsx +++ b/frontend/src/components/visual-editor/v2/CustomCanvasWidget.tsx @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 styled from "@emotion/styled"; import { CanvasEngine, @@ -14,6 +15,8 @@ import { } from "@projectstorm/react-diagrams"; import * as React from "react"; +import { BackgroundLayerWidget } from "./BackgroundLayerWidget"; + export interface DiagramProps { engine: CanvasEngine; className?: string; @@ -115,6 +118,7 @@ export class CustomCanvasWidget extends React.Component { this.props.engine.getActionEventBus().fireAction({ event }); }} > + ; {model.getLayers().map((layer) => { return ( diff --git a/frontend/src/components/visual-editor/v2/CustomDiagramNodes/NodeWidget.tsx b/frontend/src/components/visual-editor/v2/CustomDiagramNodes/NodeWidget.tsx index 5a330780..f0db023b 100644 --- a/frontend/src/components/visual-editor/v2/CustomDiagramNodes/NodeWidget.tsx +++ b/frontend/src/components/visual-editor/v2/CustomDiagramNodes/NodeWidget.tsx @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 BrokenImageOutlinedIcon from "@mui/icons-material/BrokenImageOutlined"; import ChatBubbleOutlineOutlinedIcon from "@mui/icons-material/ChatBubbleOutlineOutlined"; import ExtensionOutlinedIcon from "@mui/icons-material/ExtensionOutlined"; @@ -76,7 +77,7 @@ class NodeAbstractWidget extends React.Component< > {this.props.node.starts_conversation ? (
-
+
) : null}
{ }; const isAuthenticated = !!user; + useSubscribeBroadcastChannel("login", () => { + router.reload(); + }); + + useSubscribeBroadcastChannel("logout", () => { + router.reload(); + }); + useEffect(() => { const search = location.search; diff --git a/frontend/src/contexts/broadcast-channel.context.tsx b/frontend/src/contexts/broadcast-channel.context.tsx new file mode 100644 index 00000000..4b09c054 --- /dev/null +++ b/frontend/src/contexts/broadcast-channel.context.tsx @@ -0,0 +1,115 @@ +/* + * Copyright © 2025 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 { + createContext, + FC, + ReactNode, + useContext, + useEffect, + useRef, +} from "react"; + +export enum EBCEvent { + LOGIN = "login", + LOGOUT = "logout", +} + +type BroadcastChannelMessage = { + event: `${EBCEvent}`; + data?: string | number | boolean | Record | undefined | null; +}; + +interface IBroadcastChannelProps { + channelName: string; + children: ReactNode; +} + +interface IBroadcastChannelContext { + subscribe: ( + event: `${EBCEvent}`, + callback: (message: BroadcastChannelMessage) => void, + ) => void; + postMessage: (message: BroadcastChannelMessage) => void; +} + +export const BroadcastChannelContext = createContext< + IBroadcastChannelContext | undefined +>(undefined); + +export const BroadcastChannelProvider: FC = ({ + children, + channelName, +}) => { + const channelRef = useRef( + new BroadcastChannel(channelName), + ); + const subscribersRef = useRef< + Record< + string, + Array["1"]> + > + >({}); + + useEffect(() => { + const handleMessage = ({ data }: MessageEvent) => { + subscribersRef.current[data.event].forEach((callback) => callback(data)); + }; + + channelRef.current.addEventListener("message", handleMessage); + + return () => { + channelRef.current.removeEventListener("message", handleMessage); + channelRef.current.close(); + }; + }, []); + + const subscribe: IBroadcastChannelContext["subscribe"] = ( + event, + callback, + ) => { + subscribersRef.current[event] ??= []; + subscribersRef.current[event].push(callback); + + return () => { + const index = subscribersRef.current[event].indexOf(callback); + + if (index !== -1) { + subscribersRef.current[event].splice(index, 1); + } + }; + }; + const postMessage: IBroadcastChannelContext["postMessage"] = (message) => { + channelRef.current.postMessage(message); + }; + + return ( + + {children} + + ); +}; + +export const useBroadcastChannel = () => { + const context = useContext(BroadcastChannelContext); + + if (context === undefined) { + throw new Error( + "useBroadcastChannel must be used within a BroadcastChannelProvider", + ); + } + + return context; +}; + +export default BroadcastChannelProvider; diff --git a/frontend/src/hooks/entities/auth-hooks.ts b/frontend/src/hooks/entities/auth-hooks.ts index e3a1b07b..2e829206 100755 --- a/frontend/src/hooks/entities/auth-hooks.ts +++ b/frontend/src/hooks/entities/auth-hooks.ts @@ -9,6 +9,7 @@ import { useEffect } from "react"; import { useMutation, useQuery, useQueryClient } from "react-query"; +import { useBroadcastChannel } from "@/contexts/broadcast-channel.context"; import { EntityType, TMutationOptions } from "@/services/types"; import { ILoginAttributes } from "@/types/auth/login.types"; import { @@ -32,12 +33,17 @@ export const useLogin = ( >, ) => { const { apiClient } = useApiClient(); + const { postMessage } = useBroadcastChannel(); return useMutation({ ...options, async mutationFn(credentials) { return await apiClient.login(credentials); }, + onSuccess: (data, variables, context) => { + options?.onSuccess?.(data, variables, context); + postMessage({ event: "login" }); + }, }); }; @@ -58,6 +64,7 @@ export const useLogout = ( const { logoutRedirection } = useLogoutRedirection(); const { toast } = useToast(); const { t } = useTranslate(); + const { postMessage } = useBroadcastChannel(); return useMutation({ ...options, @@ -68,6 +75,7 @@ export const useLogout = ( }, onSuccess: async () => { queryClient.removeQueries([CURRENT_USER_KEY]); + postMessage({ event: "logout" }); await logoutRedirection(); toast.success(t("message.logout_success")); }, diff --git a/frontend/src/hooks/useSubscribeBroadcastChannel.ts b/frontend/src/hooks/useSubscribeBroadcastChannel.ts new file mode 100644 index 00000000..cb43d5a2 --- /dev/null +++ b/frontend/src/hooks/useSubscribeBroadcastChannel.ts @@ -0,0 +1,21 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useEffect } from "react"; + +import { useBroadcastChannel } from "@/contexts/broadcast-channel.context"; + +export const useSubscribeBroadcastChannel: ReturnType< + typeof useBroadcastChannel +>["subscribe"] = (...props) => { + const { subscribe } = useBroadcastChannel(); + + useEffect(() => { + subscribe(...props); + }, [subscribe, ...props]); +}; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index d41af17f..bfbca108 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -19,6 +19,7 @@ import { ReactQueryDevtools } from "react-query/devtools"; import { SnackbarCloseButton } from "@/app-components/displays/Toast/CloseButton"; import { ApiClientProvider } from "@/contexts/apiClient.context"; import { AuthProvider } from "@/contexts/auth.context"; +import BroadcastChannelProvider from "@/contexts/broadcast-channel.context"; import { ConfigProvider } from "@/contexts/config.context"; import { DialogsProvider } from "@/contexts/dialogs.context"; import { PermissionProvider } from "@/contexts/permission.context"; @@ -85,15 +86,17 @@ const App = ({ Component, pageProps }: TAppPropsWithLayout) => { - - - - - {getLayout()} - - - - + + + + + + {getLayout()} + + + + + diff --git a/frontend/src/styles/visual-editor.css b/frontend/src/styles/visual-editor.css index b04626b9..c1ec9d0d 100644 --- a/frontend/src/styles/visual-editor.css +++ b/frontend/src/styles/visual-editor.css @@ -18,20 +18,14 @@ .diagram-container { width: 100%; height: 100%; - background: #e8eff2; - background-image: linear-gradient(to right, #fff9 1px, transparent 1px), - linear-gradient(to bottom, #fff9 1px, transparent 1px); - background-size: 20px 20px; - background-position: -1px -1px; - background-attachment: fixed; z-index: 2; } .start-point-container { top: -11px; left: -11px; - width: 22px; - height: 22px; + width: 20px; + height: 20px; transform: scale(140%); border: 1px solid #fff; position: absolute; @@ -40,12 +34,12 @@ } .start-point { - margin: 5px 7px; - width: 0; - height: 0; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 9px solid #fff; + color: #FFF; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 18px; } .node { @@ -58,7 +52,7 @@ z-index: -1; } .node:has(.selected) { - outline: 6px solid #1dc7fc; + outline: 2px solid #1dc7fc; z-index: 0; cursor: grab; transform: scale(1.02); @@ -76,7 +70,8 @@ min-height: 130px; background-color: #fff; padding: 0px; - border-radius: 17.5px; + border-radius: 12px; + box-shadow: 0 0 8px #c4c4c4; } .custom-node-header { @@ -116,7 +111,7 @@ justify-content: flex-start; align-items: center; gap: 15px; - padding: 20px 24px 24px; + padding: 16px 28px; position: relative; } @@ -127,7 +122,7 @@ justify-items: flex-start; align-items: center; gap: 8px; - border-radius: 10px 10px 0 0px; + border-radius: 12px 12px 0 0; width: auto; margin-top: -1px; margin-left: -1px; @@ -186,6 +181,9 @@ .circle-porter { top: 50%; margin-top: 5px; + border-radius: 100%; + box-shadow: 0 0 8px #0003; + transition: all .4s ease 0s; } .circle-out-porters { position: absolute; @@ -196,9 +194,20 @@ .circle-porter-in { position: absolute; top: 50%; - transform: translateY(-50%); + transform: translateY(-50%) scale(.75); left: -12px; } .circle-porter-out { - right: -30px; + transform: scale(.75); + right: -12px; +} + +.circle-porter-out:hover { + cursor: grab; + transform: scale(1.1); +} + +.circle-porter-in:hover { + cursor: grab; + transform: translateY(-50%) scale(1.1); } diff --git a/frontend/src/utils/attachment.ts b/frontend/src/utils/attachment.ts index 93254491..b68cfd59 100644 --- a/frontend/src/utils/attachment.ts +++ b/frontend/src/utils/attachment.ts @@ -6,24 +6,46 @@ * 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 { IAttachment } from "@/types/attachment.types"; import { FileType, TAttachmentForeignKey } from "@/types/message.types"; import { buildURL } from "./URL"; export const MIME_TYPES = { - images: ["image/jpeg", "image/png", "image/gif", "image/webp"], - videos: ["video/mp4", "video/webm", "video/ogg"], - audios: ["audio/mpeg", "audio/ogg", "audio/wav"], + images: ["image/jpeg", "image/png", "image/webp", "image/bmp"], + videos: [ + "video/mp4", + "video/webm", + "video/ogg", + "video/quicktime", // common in apple devices + "video/x-msvideo", // AVI + "video/x-matroska", // MKV + ], + audios: [ + "audio/mpeg", // MP3 + "audio/mp3", // Explicit MP3 type + "audio/ogg", + "audio/wav", + "audio/aac", // common in apple devices + "audio/x-wav", + ], documents: [ "application/pdf", - "application/msword", + "application/msword", // older ms Word format + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // newer ms word format "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/vnd.ms-excel", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel", // older excel format + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // newer excel format "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/plain", + "application/rtf", + "application/epub", // do we want to support epub? + "application/x-7z-compressed", // do we want to support 7z? + "application/zip", // do we want to support zip? + "application/x-rar-compressed", // do we want to support winrar? + "application/json", + "text/csv", ], }; @@ -68,4 +90,4 @@ export function extractFilenameFromUrl(url: string) { // If the URL is invalid, return the input as-is return url; } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 2372b00e..a201be15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hexabot", - "version": "2.2.2", + "version": "2.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hexabot", - "version": "2.2.2", + "version": "2.2.3", "license": "AGPL-3.0-only", "workspaces": [ "frontend", diff --git a/widget/src/UiChatWidget.css b/widget/src/UiChatWidget.css index ada166e5..7697aada 100644 --- a/widget/src/UiChatWidget.css +++ b/widget/src/UiChatWidget.css @@ -3,5 +3,5 @@ right: 25px !important; bottom: 25px !important; z-index: 999 !important; - box-shadow: 0 0 8px #0003 !important; + box-shadow: 0 0 8px #c4c4c4 !important; } diff --git a/widget/src/UiChatWidget.tsx b/widget/src/UiChatWidget.tsx index 96dc5de4..32290344 100644 --- a/widget/src/UiChatWidget.tsx +++ b/widget/src/UiChatWidget.tsx @@ -1,15 +1,17 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 { PropsWithChildren } from "react"; import Launcher from "./components/Launcher"; import UserSubscription from "./components/UserSubscription"; +import BroadcastChannelProvider from "./providers/BroadcastChannelProvider"; import ChatProvider from "./providers/ChatProvider"; import { ColorProvider } from "./providers/ColorProvider"; import { ConfigProvider } from "./providers/ConfigProvider"; @@ -41,17 +43,19 @@ function UiChatWidget({ - - - - - + + + + + + + diff --git a/widget/src/components/buttons/FileButton.tsx b/widget/src/components/buttons/FileButton.tsx index 22aa0117..3011a4ff 100644 --- a/widget/src/components/buttons/FileButton.tsx +++ b/widget/src/components/buttons/FileButton.tsx @@ -1,14 +1,15 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 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 React, { ChangeEvent } from "react"; +import React, { ChangeEvent, useMemo } from "react"; import { useChat } from "../../providers/ChatProvider"; +import { MIME_TYPES } from "../../utils/attachment"; import FileInputIcon from "../icons/FileInputIcon"; import "./FileButton.scss"; @@ -23,12 +24,17 @@ const FileButton: React.FC = () => { setFile && setFile(e.target.files[0]); } }; + const acceptedMimeTypes = useMemo( + () => Object.values(MIME_TYPES).flat().join(","), + [], + ); return (