From a43bc45b73d12fb26c1de2e722310c4f752b3c99 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 8 May 2026 11:18:12 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AD=20fix:=20Preserve=20File=20Search?= =?UTF-8?q?=20Upload=20Target=20(#13019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/Input/Files/AttachFileMenu.tsx | 12 ++-- .../Files/__tests__/AttachFileMenu.spec.tsx | 61 ++++++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx index 0b10e6c712..cb1c5e53d7 100644 --- a/client/src/components/Chat/Input/Files/AttachFileMenu.tsx +++ b/client/src/components/Chat/Input/Files/AttachFileMenu.tsx @@ -81,7 +81,7 @@ const AttachFileMenu = ({ const [ephemeralAgent, setEphemeralAgent] = useRecoilState( ephemeralAgentByConvoId(conversationId), ); - const [toolResource, setToolResource] = useState(); + const toolResourceRef = useRef(); const { handleFileChange } = useFileHandlingNoChatContext(undefined, { files, setFiles, @@ -90,7 +90,7 @@ const AttachFileMenu = ({ }); const { handleSharePointFiles, isProcessing, downloadProgress } = useSharePointFileHandlingNoChatContext( - { toolResource }, + { toolResource: toolResourceRef.current }, { files, setFiles, setFilesLoading, conversation }, ); @@ -142,6 +142,10 @@ const AttachFileMenu = ({ ); const dropdownItems = useMemo(() => { + const setToolResource = (value: EToolResources | undefined) => { + toolResourceRef.current = value; + }; + const createMenuItems = (onAction: (fileType?: FileUploadType) => void) => { const items: MenuItemProps[] = []; @@ -257,7 +261,6 @@ const AttachFileMenu = ({ capabilities, useResponsesApi, handleUploadClick, - setToolResource, setEphemeralAgent, sharePointEnabled, codeAllowedByAgent, @@ -301,7 +304,8 @@ const AttachFileMenu = ({ { - handleFileChange(e, toolResource); + handleFileChange(e, toolResourceRef.current); + toolResourceRef.current = undefined; }} > ({ @@ -31,7 +31,20 @@ jest.mock('@librechat/client', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports const R = require('react'); return { - FileUpload: (props) => R.createElement('div', { 'data-testid': 'file-upload' }, props.children), + FileUpload: R.forwardRef((props, ref) => + R.createElement( + 'div', + { 'data-testid': 'file-upload' }, + props.children, + R.createElement('input', { + ref, + multiple: true, + type: 'file', + 'data-testid': 'file-input', + onChange: props.handleFileChange, + }), + ), + ), TooltipAnchor: (props) => props.render, DropdownPopup: (props) => R.createElement( @@ -318,6 +331,50 @@ describe('AttachFileMenu', () => { expect(screen.getByText('Upload for File Search')).toBeInTheDocument(); expect(screen.getByText('Upload Code Files')).toBeInTheDocument(); }); + + it('passes File Search resource when the file input changes before React state commits', () => { + setupMocks(); + const handleFileChange = jest.fn(); + mockUseFileHandlingNoChatContext.mockReturnValue({ handleFileChange }); + mockUseAgentCapabilities.mockReturnValue({ + contextEnabled: false, + fileSearchEnabled: true, + codeEnabled: false, + }); + mockUseAgentToolPermissions.mockReturnValue({ + fileSearchAllowedByAgent: true, + codeAllowedByAgent: false, + provider: undefined, + }); + const originalClick = HTMLInputElement.prototype.click; + const file = new File(['data'], 'sheet.xlsx', { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }); + + HTMLInputElement.prototype.click = function click() { + Object.defineProperty(this, 'files', { + configurable: true, + value: [file], + }); + fireEvent.change(this); + }; + + try { + renderMenu({ endpointType: EModelEndpoint.openAI }); + openMenu(); + fireEvent.click(screen.getByText('Upload to Provider')); + fireEvent.click(screen.getByText('Upload for File Search')); + } finally { + HTMLInputElement.prototype.click = originalClick; + } + + expect(handleFileChange).toHaveBeenNthCalledWith(1, expect.any(Object), undefined); + expect(handleFileChange).toHaveBeenNthCalledWith( + 2, + expect.any(Object), + EToolResources.file_search, + ); + }); }); describe('SharePoint Integration', () => {