From d65accddc1308cd79ec0d05cbc559bb3040bf8cc Mon Sep 17 00:00:00 2001 From: Dustin Healy Date: Fri, 5 Sep 2025 19:34:38 -0700 Subject: [PATCH] feat: add AttachFileButton for uploading files from a prompt context rather than chat This is pretty much a stripped down version of AttachFileMenu so ofc there is duplication across this new component and AttachFileMenu but I believe it outweighs the increased complexity that would come from attempting to handle both contexts within just AttachFileMenu in regards to ephemeral agents and the file handling hooks - though we could probably refactor this without too much hassle later on in the file upload unification push once things are more settled. --- .../Prompts/Files/AttachFileButton.tsx | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 client/src/components/Prompts/Files/AttachFileButton.tsx diff --git a/client/src/components/Prompts/Files/AttachFileButton.tsx b/client/src/components/Prompts/Files/AttachFileButton.tsx new file mode 100644 index 0000000000..cf7f3c5347 --- /dev/null +++ b/client/src/components/Prompts/Files/AttachFileButton.tsx @@ -0,0 +1,101 @@ +import * as Ariakit from '@ariakit/react'; +import { EToolResources } from 'librechat-data-provider'; +import React, { useRef, useState, useMemo, useCallback } from 'react'; +import { FileUpload, DropdownPopup, AttachmentIcon } from '@librechat/client'; +import { FileSearch, ImageUpIcon, TerminalSquareIcon, FileType2Icon } from 'lucide-react'; +import { useLocalize } from '~/hooks'; + +interface AttachFileButtonProps { + handleFileChange?: (event: React.ChangeEvent, toolResource?: string) => void; + disabled?: boolean | null; +} + +const AttachFileButton = ({ handleFileChange, disabled }: AttachFileButtonProps) => { + const localize = useLocalize(); + const isUploadDisabled = disabled ?? false; + const inputRef = useRef(null); + const [isPopoverActive, setIsPopoverActive] = useState(false); + const [toolResource, setToolResource] = useState(); + + const handleUploadClick = useCallback((isImage?: boolean) => { + if (!inputRef.current) { + return; + } + inputRef.current.value = ''; + inputRef.current.accept = isImage === true ? 'image/*' : ''; + inputRef.current.click(); + inputRef.current.accept = ''; + }, []); + + const dropdownItems = useMemo(() => { + return [ + { + label: localize('com_ui_upload_image_input'), + onClick: () => { + setToolResource(EToolResources.image_edit); + handleUploadClick(true); + }, + icon: , + }, + { + label: localize('com_ui_upload_ocr_text'), + onClick: () => { + setToolResource(EToolResources.ocr); + handleUploadClick(); + }, + icon: , + }, + { + label: localize('com_ui_upload_file_search'), + onClick: () => { + setToolResource(EToolResources.file_search); + handleUploadClick(); + }, + icon: , + }, + { + label: localize('com_ui_upload_code_files'), + onClick: () => { + setToolResource(EToolResources.execute_code); + handleUploadClick(); + }, + icon: , + }, + ]; + }, [localize, handleUploadClick]); + + const menuTrigger = ( + + + {localize('com_ui_attach_files')} + + ); + + return ( + { + handleFileChange?.(e, toolResource); + }} + > + + + ); +}; + +export default React.memo(AttachFileButton);