UNPKG

@memori.ai/memori-react

Version:

[![npm version](https://img.shields.io/github/package-json/v/memori-ai/memori-react)](https://www.npmjs.com/package/@memori.ai/memori-react) ![Tests](https://github.com/memori-ai/memori-react/workflows/CI/badge.svg?branch=main) ![TypeScript Support](https

218 lines (216 loc) 12.4 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useRef, useEffect } from 'react'; import { DocumentIcon } from '../icons/Document'; import { ImageIcon } from '../icons/Image'; import { UploadIcon } from '../icons/Upload'; import Spin from '../ui/Spin'; import Alert from '../ui/Alert'; import cx from 'classnames'; import UploadDocuments from './UploadDocuments/UploadDocuments'; import UploadImages from './UploadImages/UploadImages'; import { useTranslation } from 'react-i18next'; import { MAX_DOCUMENTS_PER_MESSAGE } from '../../helpers/constants'; const MAX_IMAGES = 5; const MAX_DOCUMENTS = MAX_DOCUMENTS_PER_MESSAGE; const UploadButton = ({ authToken = '', client, sessionID = '', isMediaAccepted = false, setDocumentPreviewFiles, documentPreviewFiles, memoriID = '', }) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const [isLoading, setIsLoading] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const [errors, setErrors] = useState([]); const { t, i18n } = useTranslation(); const menuRef = useRef(null); const buttonRef = useRef(null); const documentRef = useRef(null); const imageRef = useRef(null); const currentImageCount = documentPreviewFiles.filter(file => file.type === 'image').length; const remainingSlots = MAX_IMAGES - currentImageCount; const currentDocumentCount = documentPreviewFiles.filter(file => file.type === 'document').length; const remainingDocumentSlots = MAX_DOCUMENTS - currentDocumentCount; const hasReachedImageLimit = remainingSlots <= 0; const hasReachedDocumentLimit = remainingDocumentSlots <= 0; const removeError = (errorMessage) => { setErrors(prev => prev.filter(e => e.message !== errorMessage)); }; const addError = (error) => { setErrors(prev => [...prev, error]); setTimeout(() => removeError(error.message), 5000); }; const toggleMenu = () => { setMenuOpen(prev => !prev); }; const closeMenu = () => { setMenuOpen(false); }; useEffect(() => { const handleClickOutside = (event) => { if (menuRef.current && buttonRef.current && !menuRef.current.contains(event.target) && !buttonRef.current.contains(event.target)) { closeMenu(); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const handleDocumentFiles = (files) => { if (files.length === 0) return; const escapeAttributeValue = (text) => { return text .replace(/&/g, '&amp;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;'); }; const processedDocuments = files.map(file => { const escapedFileName = escapeAttributeValue(file.name); const formattedContent = `<document_attachment filename="${escapedFileName}" type="${file.mimeType}"> ${file.content} </document_attachment>`; return { name: file.name, id: file.id, content: formattedContent, type: 'document', mimeType: file.mimeType, }; }); const imageFiles = documentPreviewFiles.filter((file) => file.type === 'image'); setDocumentPreviewFiles([ ...processedDocuments, ...imageFiles, ]); setIsLoading(false); }; const validateDocumentFile = (file) => { var _a; const fileExt = `.${(_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()}`; const ALLOWED_FILE_TYPES = ['.pdf', '.txt', '.json', '.xlsx', '.csv', '.md']; const MAX_FILE_SIZE = 10 * 1024 * 1024; if (!ALLOWED_FILE_TYPES.includes(fileExt)) { addError({ message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(', ')}`, severity: 'error', }); return false; } if (file.size > MAX_FILE_SIZE) { addError({ message: `File "${file.name}" exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit`, severity: 'error', }); return false; } return true; }; const validatePayloadSize = (newDocuments) => { const { MAX_TOTAL_MESSAGE_PAYLOAD } = require('../../helpers/constants'); const existingDocuments = documentPreviewFiles.filter((file) => file.type === 'document'); const allDocuments = [...existingDocuments, ...newDocuments]; const totalPayloadSize = allDocuments.reduce((total, doc) => total + doc.content.length, 0); if (totalPayloadSize > MAX_TOTAL_MESSAGE_PAYLOAD) { addError({ message: `Total document content exceeds ${MAX_TOTAL_MESSAGE_PAYLOAD} characters limit. Please remove some documents.`, severity: 'error', }); return false; } return true; }; const handleDocumentError = (error) => { addError(error); }; const validateImageFile = (file) => { var _a; const fileExt = `.${(_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()}`; const ALLOWED_FILE_TYPES = ['.jpg', '.jpeg', '.png']; const MAX_FILE_SIZE = 10 * 1024 * 1024; if (!ALLOWED_FILE_TYPES.includes(fileExt) && !file.type.startsWith('image/')) { addError({ message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(', ')}`, severity: 'error', }); return false; } if (file.size > MAX_FILE_SIZE) { addError({ message: `File "${file.name}" exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit`, severity: 'error', }); return false; } return true; }; const handleImageError = (error) => { addError(error); }; const handleDocumentClick = () => { var _a; if (hasReachedDocumentLimit) { addError({ message: `Maximum ${MAX_DOCUMENTS} documents allowed.`, severity: 'error', }); closeMenu(); return; } const documentButtonElement = (_a = documentRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('button'); if (documentButtonElement) { documentButtonElement.click(); } closeMenu(); }; const handleImageClick = () => { var _a, _b, _c; if (!isMediaAccepted) { addError({ message: (_a = t('upload.mediaNotAccepted')) !== null && _a !== void 0 ? _a : 'Media uploads are not accepted', severity: 'info', }); closeMenu(); return; } if (hasReachedImageLimit) { addError({ message: (_b = t('upload.maxImagesReached', { max: MAX_IMAGES })) !== null && _b !== void 0 ? _b : `Maximum ${MAX_IMAGES} images already uploaded`, severity: 'warning', }); closeMenu(); return; } const imageButtonElement = (_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.querySelector('button'); if (imageButtonElement) { imageButtonElement.click(); } closeMenu(); }; const handleLoadingChange = (loading) => { setIsLoading(loading); }; return (_jsxs("div", { className: "memori--unified-upload-wrapper", children: [_jsx("button", { ref: buttonRef, className: cx('memori-button', 'memori-button--circle', 'memori-button--icon-only', 'memori-share-button--button', 'memori--conversation-button', 'memori--unified-upload-button', { 'memori--error': errors.length > 0 }), onClick: toggleMenu, disabled: isLoading, title: (_a = t('upload.uploadFiles')) !== null && _a !== void 0 ? _a : 'Upload files', children: isLoading ? (_jsx(Spin, { spinning: true, className: "memori--upload-icon" })) : (_jsx(UploadIcon, { className: "memori--upload-icon" })) }), currentImageCount > 0 && (_jsxs("div", { className: cx('memori--image-count', { 'memori--image-count-full': hasReachedImageLimit, }), children: [currentImageCount, "/", MAX_IMAGES] })), currentDocumentCount > 0 && (_jsxs("div", { className: cx('memori--document-count', { 'memori--document-count-full': hasReachedDocumentLimit, }), children: [currentDocumentCount, "/", MAX_DOCUMENTS] })), menuOpen && (_jsxs("div", { className: "memori--upload-menu", ref: menuRef, children: [_jsxs("div", { className: cx('memori--upload-menu-item', { 'memori--upload-menu-item--disabled': hasReachedDocumentLimit, }), onClick: handleDocumentClick, title: hasReachedDocumentLimit ? (_b = t('upload.maxDocumentsReached', { max: MAX_DOCUMENTS })) !== null && _b !== void 0 ? _b : `Maximum ${MAX_DOCUMENTS} documents already uploaded` : remainingDocumentSlots === 1 ? (_c = t('upload.lastDocumentSlot')) !== null && _c !== void 0 ? _c : 'Upload last document' : (_d = t('upload.uploadDocument', { remaining: remainingDocumentSlots })) !== null && _d !== void 0 ? _d : `Upload document (${remainingDocumentSlots} remaining)`, children: [_jsx(DocumentIcon, { className: "memori--upload-menu-icon" }), _jsx("span", { children: (_e = t('upload.uploadDocument')) !== null && _e !== void 0 ? _e : 'Upload document' })] }), _jsxs("div", { className: cx('memori--upload-menu-item', { 'memori--upload-menu-item--disabled': !isMediaAccepted || hasReachedImageLimit, }), onClick: handleImageClick, title: !isMediaAccepted ? (_f = t('upload.mediaNotAccepted')) !== null && _f !== void 0 ? _f : 'Media uploads not accepted' : hasReachedImageLimit ? (_g = t('upload.maxImagesReached', { max: MAX_IMAGES })) !== null && _g !== void 0 ? _g : `Maximum ${MAX_IMAGES} images already uploaded` : remainingSlots === 1 ? (_h = t('upload.lastImageSlot')) !== null && _h !== void 0 ? _h : 'Upload last image' : (_j = t('upload.uploadImage', { remaining: remainingSlots })) !== null && _j !== void 0 ? _j : `Upload image (${remainingSlots} remaining)`, children: [_jsx(ImageIcon, { className: "memori--upload-menu-icon-image" }), _jsx("span", { children: (_k = t('upload.uploadImage')) !== null && _k !== void 0 ? _k : 'Upload image' })] })] })), _jsx("div", { className: "memori--hidden-uploader", ref: documentRef, children: _jsx(UploadDocuments, { setDocumentPreviewFiles: handleDocumentFiles, maxDocuments: MAX_DOCUMENTS, documentPreviewFiles: documentPreviewFiles, onLoadingChange: handleLoadingChange, onDocumentError: handleDocumentError, onValidateFile: validateDocumentFile, onValidatePayloadSize: validatePayloadSize }) }), _jsx("div", { className: "memori--hidden-uploader", ref: imageRef, children: _jsx(UploadImages, { authToken: authToken, client: client, setDocumentPreviewFiles: setDocumentPreviewFiles, sessionID: sessionID, documentPreviewFiles: documentPreviewFiles, isMediaAccepted: isMediaAccepted, onLoadingChange: handleLoadingChange, maxImages: MAX_IMAGES, memoriID: memoriID, onImageError: handleImageError, onValidateImageFile: validateImageFile }) }), _jsx("div", { className: "memori--error-message-container", children: errors.map((error, index) => (_jsx(Alert, { className: 'memori--error-message-alert', open: true, type: error.severity, title: 'Upload notification', description: error.message, onClose: () => removeError(error.message), width: "350px" }, `${error.message}-${index}`))) })] })); }; export default UploadButton; //# sourceMappingURL=UploadButton.js.map