/* * 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 React, { useEffect, useRef, useState } from 'react'; import EmojiButton from './buttons/EmojiButton'; import FileButton from './buttons/FileButton'; import LocationButton from './buttons/LocationButton'; import MenuButton from './buttons/MenuButton'; import SendButton from './buttons/SendButton'; import CloseIcon from './icons/CloseIcon'; import FileInputIcon from './icons/FileInputIcon'; import Suggestions from './Suggestions'; import { useTranslation } from '../hooks/useTranslation'; import { useChat } from '../providers/ChatProvider'; import { useColors } from '../providers/ColorProvider'; import { useSettings } from '../providers/SettingsProvider'; import { TOutgoingMessageType } from '../types/message.types'; import { OutgoingMessageState } from '../types/state.types'; import './UserInput.scss'; const UserInput: React.FC = () => { const { t } = useTranslation(); const { colors } = useColors(); const { suggestions, message, setMessage, file, setFile, send, outgoingMessageState, } = useChat(); const { menu, focusOnOpen, autoFlush, allowedUploadTypes, allowedUploadSize, showEmoji, showLocation, showFile, placeholder, } = useSettings(); const userInputRef = useRef(null); const [fileError, setFileError] = useState(null); const [inputActive, setInputActive] = useState(false); useEffect(() => { // if (userInputRef.current) { // userInputRef.current.innerHTML = message.current || ''; // } if (focusOnOpen) { focusUserInput(); } }, [message, focusOnOpen]); useEffect(() => { if (message === '') { userInputRef.current!.innerHTML = ''; } }, [message]); useEffect(() => { setFileError(null); }, [file]); const cancelFile = () => { setFile(null); setFileError(null); }; const handleInput = () => { setMessage( userInputRef.current?.innerText || userInputRef.current?.textContent || '', ); }; const sendMessage = ( event: React.MouseEvent | React.KeyboardEvent, source: string = 'send-button', ) => { if (message) { send({ event, source, data: { type: TOutgoingMessageType.text, data: { text: message }, }, }); if (autoFlush) { setMessage(''); } } if (file) { setFileError(null); const typeCheck = allowedUploadTypes.includes(file.type) || false; if (!typeCheck) { setFileError(t('messages.file_message.unsupported_file_type')); } else if (file.size > (allowedUploadSize || 0)) { setFileError(t('messages.file_message.unsupported_file_size')); } else { send({ event, source, data: { type: TOutgoingMessageType.file, data: { name: file.name, size: file.size, type: file.type, file, }, }, }); autoFlush && setFile(null); } } }; const handleKey = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && !event.shiftKey) { sendMessage(event, 'enter-key'); event.preventDefault(); } }; const focusUserInput = () => { userInputRef.current?.focus(); }; const uploading = outgoingMessageState === OutgoingMessageState.uploading; return (
{suggestions.length > 0 && } {(file || uploading) && (
{fileError && {fileError}} {uploading && Loading...} {file && file.name && !fileError && ( {file.name.length > 23 ? `${file.name.substring(0, 23)}...` : file.name} )}
)}
{menu.length > 0 && }
setInputActive(true)} onBlur={() => setInputActive(false)} onKeyDown={handleKey} onInput={handleInput} onPaste={(e) => { e.preventDefault(); (e.target as HTMLInputElement).innerText = e.clipboardData.getData('text/plain'); handleInput(); }} contentEditable suppressContentEditableWarning={true} spellCheck aria-autocomplete="list" // @ts-expect-error to check placeholder={placeholder} // Adjust for localization className="sc-user-input--text" ref={userInputRef} style={{ color: colors.userInput.text }} />
{showEmoji && (
)} {showLocation && (
)} {showFile && (
)}
sendMessage(event)} />
); }; export default UserInput;