From 838a2af57aa6bce16d529893f142d417c64f5493 Mon Sep 17 00:00:00 2001 From: Ravishekhar Yadav Date: Thu, 16 Oct 2025 16:48:09 +0530 Subject: [PATCH 1/5] fix: XYNE-157 fixed attachement cancel while uploading --- frontend/src/components/ChatBox.tsx | 35 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index aecde7b7b..e28d9269b 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -86,6 +86,7 @@ interface SelectedFile { uploadError?: string preview?: string // URL for image preview fileType?: FileType + abortController?: AbortController } export const getFileIcon = (fileType: FileType | string | undefined) => { @@ -831,18 +832,24 @@ export const ChatBox = React.forwardRef( setUploadingFilesCount((prev) => prev + files.length) const uploadedMetadata: AttachmentMetadata[] = [] + const fileAbortControllers = new Map(files.map(file => [file.id, new AbortController()])) + // Set all files to uploading state setSelectedFiles((prev) => - prev.map((f) => - files.some((file) => file.id === f.id) - ? { ...f, uploading: true, uploadError: undefined } - : f, - ), + prev.map((f) => { + const abortController = fileAbortControllers.get(f.id) + if (abortController) { + return { ...f, uploading: true, uploadError: undefined, abortController } + } + return f + }), ) const uploadPromises = files.map(async (selectedFile) => { + const abortController = fileAbortControllers.get(selectedFile.id) try { + const formData = new FormData() formData.append("attachment", selectedFile.file) const response = await authFetch( @@ -850,6 +857,7 @@ export const ChatBox = React.forwardRef( { method: "POST", body: formData, + signal: abortController?.signal, }, ) @@ -865,7 +873,7 @@ export const ChatBox = React.forwardRef( setSelectedFiles((prev) => prev.map((f) => f.id === selectedFile.id - ? { ...f, uploading: false, metadata } + ? { ...f, uploading: false, metadata , abortController: undefined } : f, ), ) @@ -874,12 +882,15 @@ export const ChatBox = React.forwardRef( throw new Error("No document ID returned from upload") } } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return null + } const errorMessage = error instanceof Error ? error.message : "Upload failed" setSelectedFiles((prev) => prev.map((f) => f.id === selectedFile.id - ? { ...f, uploading: false, uploadError: errorMessage } + ? { ...f, uploading: false, uploadError: errorMessage, abortController: undefined } : f, ), ) @@ -889,7 +900,8 @@ export const ChatBox = React.forwardRef( }) return null } finally { - setUploadingFilesCount((prev) => prev - 1) + setUploadingFilesCount(prev => Math.max(prev - 1, 0)); + } }) @@ -979,6 +991,9 @@ export const ChatBox = React.forwardRef( const removeFile = useCallback(async (id: string) => { const fileToRemove = selectedFiles.find((f) => f.id === id) + if(fileToRemove?.uploading && fileToRemove.abortController) { + fileToRemove.abortController.abort() + } // If the file has metadata with fileId (meaning it's already uploaded), delete it from the server if (fileToRemove?.metadata?.fileId) { @@ -2862,7 +2877,7 @@ export const ChatBox = React.forwardRef( @@ -2918,7 +2933,7 @@ export const ChatBox = React.forwardRef( From 51b76c728c854c09669309c5a4e2c0188450100e Mon Sep 17 00:00:00 2001 From: Ravishekhar Yadav Date: Thu, 16 Oct 2025 17:08:00 +0530 Subject: [PATCH 2/5] chore: XYNE-157 resolved comments --- frontend/src/components/ChatBox.tsx | 410 +++++++++++++++------------- 1 file changed, 217 insertions(+), 193 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index e28d9269b..7ac9fe86f 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -86,7 +86,7 @@ interface SelectedFile { uploadError?: string preview?: string // URL for image preview fileType?: FileType - abortController?: AbortController + } export const getFileIcon = (fileType: FileType | string | undefined) => { @@ -424,6 +424,7 @@ export const ChatBox = React.forwardRef( "citations", ) const [globalResults, setGlobalResults] = useState([]) + const fileAbortControllers = useRef>(new Map()); // Unified function to enhance Google Sheets items with dummy "whole sheet" options const enhanceGoogleSheetsResults = useCallback( @@ -645,13 +646,15 @@ export const ChatBox = React.forwardRef( (!selectedAgent || (selectedAgent && selectedAgent.isRagOn))) // Check if document is ready for chat based on upload status - const isDocumentReady = !uploadStatus || uploadStatus === UploadStatus.COMPLETED - + const isDocumentReady = + !uploadStatus || uploadStatus === UploadStatus.COMPLETED + // Determine if send should be disabled - const isSendDisabled = isStreaming || - retryIsStreaming || - uploadingFilesCount > 0 || - !isDocumentReady + const isSendDisabled = + isStreaming || + retryIsStreaming || + uploadingFilesCount > 0 || + !isDocumentReady // Helper function to get tooltip content for disabled send button const getSendButtonTooltipContent = (): string | undefined => { @@ -832,24 +835,26 @@ export const ChatBox = React.forwardRef( setUploadingFilesCount((prev) => prev + files.length) const uploadedMetadata: AttachmentMetadata[] = [] - const fileAbortControllers = new Map(files.map(file => [file.id, new AbortController()])) + // const fileAbortControllers = new Map( + // files.map((file) => [file.id, new AbortController()]), + // ) + files.forEach((file) => { + fileAbortControllers.current.set(file.id, new AbortController()) + }) // Set all files to uploading state setSelectedFiles((prev) => - prev.map((f) => { - const abortController = fileAbortControllers.get(f.id) - if (abortController) { - return { ...f, uploading: true, uploadError: undefined, abortController } - } - return f - }), + prev.map((f) => + files.some((file) => file.id === f.id) + ? { ...f, uploading: true, uploadError: undefined } + : f, + ), ) const uploadPromises = files.map(async (selectedFile) => { - const abortController = fileAbortControllers.get(selectedFile.id) + const abortController = fileAbortControllers.current.get(selectedFile.id) try { - const formData = new FormData() formData.append("attachment", selectedFile.file) const response = await authFetch( @@ -873,7 +878,12 @@ export const ChatBox = React.forwardRef( setSelectedFiles((prev) => prev.map((f) => f.id === selectedFile.id - ? { ...f, uploading: false, metadata , abortController: undefined } + ? { + ...f, + uploading: false, + metadata, + abortController: undefined, + } : f, ), ) @@ -882,7 +892,7 @@ export const ChatBox = React.forwardRef( throw new Error("No document ID returned from upload") } } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { + if (error instanceof Error && error.name === "AbortError") { return null } const errorMessage = @@ -890,7 +900,12 @@ export const ChatBox = React.forwardRef( setSelectedFiles((prev) => prev.map((f) => f.id === selectedFile.id - ? { ...f, uploading: false, uploadError: errorMessage, abortController: undefined } + ? { + ...f, + uploading: false, + uploadError: errorMessage, + abortController: undefined, + } : f, ), ) @@ -900,8 +915,7 @@ export const ChatBox = React.forwardRef( }) return null } finally { - setUploadingFilesCount(prev => Math.max(prev - 1, 0)); - + setUploadingFilesCount((prev) => Math.max(prev - 1, 0)) } }) @@ -989,40 +1003,44 @@ export const ChatBox = React.forwardRef( return ext || "file" } - const removeFile = useCallback(async (id: string) => { - const fileToRemove = selectedFiles.find((f) => f.id === id) - if(fileToRemove?.uploading && fileToRemove.abortController) { - fileToRemove.abortController.abort() - } - - // If the file has metadata with fileId (meaning it's already uploaded), delete it from the server - if (fileToRemove?.metadata?.fileId) { - try { - const response = await api.files.delete.$post({ - json: { - attachment: fileToRemove.metadata + const removeFile = useCallback( + async (id: string) => { + const fileToRemove = selectedFiles.find((f) => f.id === id) + const abortController = fileAbortControllers.current.get(id); + if (fileToRemove?.uploading && abortController) { + abortController.abort(); + } + + // If the file has metadata with fileId (meaning it's already uploaded), delete it from the server + if (fileToRemove?.metadata?.fileId) { + try { + const response = await api.files.delete.$post({ + json: { + attachment: fileToRemove.metadata, + }, + }) + + if (!response.ok) { + const errorText = await response.text() + console.error(`Failed to delete attachment: ${errorText}`) + // Still remove from UI even if server deletion fails } - }) - - if (!response.ok) { - const errorText = await response.text() - console.error(`Failed to delete attachment: ${errorText}`) + } catch (error) { + console.error("Error deleting attachment:", error) // Still remove from UI even if server deletion fails } - } catch (error) { - console.error('Error deleting attachment:', error) - // Still remove from UI even if server deletion fails } - } - - // Remove from UI - setSelectedFiles((prev) => { - if (fileToRemove?.preview) { - URL.revokeObjectURL(fileToRemove.preview) - } - return prev.filter((f) => f.id !== id) - }) - }, [selectedFiles]) + + // Remove from UI + setSelectedFiles((prev) => { + if (fileToRemove?.preview) { + URL.revokeObjectURL(fileToRemove.preview) + } + return prev.filter((f) => f.id !== id) + }) + }, + [selectedFiles], + ) const { handleFileSelect, handleFileChange } = createFileSelectionHandlers( fileInputRef, @@ -2022,157 +2040,159 @@ export const ChatBox = React.forwardRef( return () => document.removeEventListener("mousedown", handleOutsideClick) }, [showReferenceBox]) - const handleSendMessage = useCallback(async (isFollowUp: boolean = false) => { - const activeSourceIds = Object.entries(selectedSources) - .filter(([, isSelected]) => isSelected) - .map(([id]) => id) - - let htmlMessage = inputRef.current?.innerHTML || "" - htmlMessage = htmlMessage.replace(/( |\s)+$/g, "") - htmlMessage = htmlMessage.replace(/(\s*)+$/gi, "") - htmlMessage = htmlMessage.replace(/( |\s)+$/g, "") - - let toolsListToSend: ToolsListItem[] | undefined = undefined - - // Build toolsList from all selected connectors - if (selectedConnectorIds.size > 0) { - const toolsListArray: ToolsListItem[] = [] - - // Include tools from all selected connectors - selectedConnectorIds.forEach((connectorId) => { - const toolsSet = selectedConnectorTools[connectorId] - - if (toolsSet && toolsSet.size > 0) { - // Find the connector to get its internal connectorId - const connector = allConnectors.find((c) => c.id === connectorId) - if (connector) { - const toolsArray = Array.from(toolsSet) - toolsListArray.push({ - connectorId: connector.connectorId.toString(), // Use internal DB id - tools: toolsArray, - }) + const handleSendMessage = useCallback( + async (isFollowUp: boolean = false) => { + const activeSourceIds = Object.entries(selectedSources) + .filter(([, isSelected]) => isSelected) + .map(([id]) => id) + + let htmlMessage = inputRef.current?.innerHTML || "" + htmlMessage = htmlMessage.replace(/( |\s)+$/g, "") + htmlMessage = htmlMessage.replace(/(\s*)+$/gi, "") + htmlMessage = htmlMessage.replace(/( |\s)+$/g, "") + + let toolsListToSend: ToolsListItem[] | undefined = undefined + + // Build toolsList from all selected connectors + if (selectedConnectorIds.size > 0) { + const toolsListArray: ToolsListItem[] = [] + + // Include tools from all selected connectors + selectedConnectorIds.forEach((connectorId) => { + const toolsSet = selectedConnectorTools[connectorId] + + if (toolsSet && toolsSet.size > 0) { + // Find the connector to get its internal connectorId + const connector = allConnectors.find((c) => c.id === connectorId) + if (connector) { + const toolsArray = Array.from(toolsSet) + toolsListArray.push({ + connectorId: connector.connectorId.toString(), // Use internal DB id + tools: toolsArray, + }) + } } - } - }) + }) - // Only send toolsList if we actually have tools selected - if ( - toolsListArray.length > 0 && - toolsListArray.some((item) => item.tools.length > 0) - ) { - toolsListToSend = toolsListArray + // Only send toolsList if we actually have tools selected + if ( + toolsListArray.length > 0 && + toolsListArray.some((item) => item.tools.length > 0) + ) { + toolsListToSend = toolsListArray + } } - } - // Handle Attachments Metadata - let attachmentsMetadata: AttachmentMetadata[] = [] - if (selectedFiles.length > 0) { - if (uploadingFilesCount > 0) { - await new Promise((resolve) => { - uploadCompleteResolver.current = resolve - if(uploadingFilesCount==0){ - resolve(); - uploadCompleteResolver.current=null - } - }) + // Handle Attachments Metadata + let attachmentsMetadata: AttachmentMetadata[] = [] + if (selectedFiles.length > 0) { + if (uploadingFilesCount > 0) { + await new Promise((resolve) => { + uploadCompleteResolver.current = resolve + if (uploadingFilesCount == 0) { + resolve() + uploadCompleteResolver.current = null + } + }) + } + const alreadyUploadedMetadata = selectedFiles + .map((f) => f.metadata) + .filter((m): m is AttachmentMetadata => !!m) + attachmentsMetadata = alreadyUploadedMetadata } - const alreadyUploadedMetadata = selectedFiles - .map((f) => f.metadata) - .filter((m): m is AttachmentMetadata => !!m) - - attachmentsMetadata = alreadyUploadedMetadata - } - // Replace data-doc-id and data-reference-id with mailId - const tempDiv = document.createElement("div") - tempDiv.innerHTML = htmlMessage - const pills = tempDiv.querySelectorAll("a.reference-pill") - - pills.forEach((pill) => { - const mailId = pill.getAttribute("data-mail-id") - const userMap = pill.getAttribute("user-map") - const threadId = pill.getAttribute("data-thread-id") - const docId = - pill.getAttribute("data-doc-id") || - pill.getAttribute("data-reference-id") - if (userMap) { - try { - const parsedUserMap = JSON.parse(userMap) - if (user?.email && parsedUserMap[user.email]) { - pill.setAttribute( - "href", - `https://mail.google.com/mail/u/0/#inbox/${parsedUserMap[user.email]}`, - ) - } else { - console.warn( - `No mapping found for user email: ${user?.email} in userMap.`, - ) + // Replace data-doc-id and data-reference-id with mailId + const tempDiv = document.createElement("div") + tempDiv.innerHTML = htmlMessage + const pills = tempDiv.querySelectorAll("a.reference-pill") + + pills.forEach((pill) => { + const mailId = pill.getAttribute("data-mail-id") + const userMap = pill.getAttribute("user-map") + const threadId = pill.getAttribute("data-thread-id") + const docId = + pill.getAttribute("data-doc-id") || + pill.getAttribute("data-reference-id") + if (userMap) { + try { + const parsedUserMap = JSON.parse(userMap) + if (user?.email && parsedUserMap[user.email]) { + pill.setAttribute( + "href", + `https://mail.google.com/mail/u/0/#inbox/${parsedUserMap[user.email]}`, + ) + } else { + console.warn( + `No mapping found for user email: ${user?.email} in userMap.`, + ) + } + } catch (error) { + console.error("Failed to parse userMap:", error) } - } catch (error) { - console.error("Failed to parse userMap:", error) } - } - if (mailId) { - pill.setAttribute("data-doc-id", mailId) - pill.setAttribute("data-reference-id", mailId) - pill.setAttribute("data-thread-id", threadId || "") - } else { - console.warn( - `No mailId found for pill with docId: ${docId}. Skipping replacement.`, - ) + if (mailId) { + pill.setAttribute("data-doc-id", mailId) + pill.setAttribute("data-reference-id", mailId) + pill.setAttribute("data-thread-id", threadId || "") + } else { + console.warn( + `No mailId found for pill with docId: ${docId}. Skipping replacement.`, + ) + } + }) + + htmlMessage = tempDiv.innerHTML + + // Prepare model configuration with capability flags + const modelConfig = { + model: selectedModel, + reasoning: selectedCapability === "reasoning", + websearch: selectedCapability === "websearch", + deepResearch: selectedCapability === "deepResearch", } - }) - htmlMessage = tempDiv.innerHTML + handleSend( + htmlMessage, + attachmentsMetadata, + activeSourceIds.length > 0 ? activeSourceIds : undefined, + persistedAgentId, + toolsListToSend, + JSON.stringify(modelConfig), // Send model config as JSON string + isFollowUp, + ) - // Prepare model configuration with capability flags - const modelConfig = { - model: selectedModel, - reasoning: selectedCapability === "reasoning", - websearch: selectedCapability === "websearch", - deepResearch: selectedCapability === "deepResearch", - } + // Clear the input and attached files after sending + if (inputRef.current) { + inputRef.current.innerHTML = "" + } + setQuery("") - handleSend( - htmlMessage, - attachmentsMetadata, - activeSourceIds.length > 0 ? activeSourceIds : undefined, + // Cleanup preview URLs before clearing files + const previewUrls = selectedFiles + .map((f) => f.preview) + .filter(Boolean) as string[] + cleanupPreviewUrls(previewUrls) + setSelectedFiles([]) + }, + [ + selectedSources, + selectedConnectorIds, + selectedConnectorTools, + allConnectors, + selectedFiles, persistedAgentId, - toolsListToSend, - JSON.stringify(modelConfig), // Send model config as JSON string - isFollowUp, - ) - - // Clear the input and attached files after sending - if (inputRef.current) { - inputRef.current.innerHTML = "" - } - setQuery("") - - // Cleanup preview URLs before clearing files - const previewUrls = selectedFiles - .map((f) => f.preview) - .filter(Boolean) as string[] - cleanupPreviewUrls(previewUrls) - setSelectedFiles([]) - }, [ - selectedSources, - selectedConnectorIds, - selectedConnectorTools, - allConnectors, - selectedFiles, - persistedAgentId, - selectedModel, - selectedCapability, - handleSend, - uploadFiles, - user, - setQuery, - setSelectedFiles, - cleanupPreviewUrls, - ]) + selectedModel, + selectedCapability, + handleSend, + uploadFiles, + user, + setQuery, + setSelectedFiles, + cleanupPreviewUrls, + ], + ) const handleSourceSelectionChange = ( sourceId: string, @@ -2877,7 +2897,6 @@ export const ChatBox = React.forwardRef( @@ -2933,7 +2952,6 @@ export const ChatBox = React.forwardRef( @@ -3954,7 +3972,9 @@ export const ChatBox = React.forwardRef( ) : ( (() => { - const tooltipContent = isSendDisabled ? getSendButtonTooltipContent() : undefined; + const tooltipContent = isSendDisabled + ? getSendButtonTooltipContent() + : undefined const button = ( - ); + ) - return tooltipContent ? {button} : button; + return tooltipContent ? ( + {button} + ) : ( + button + ) })() )} From 5caa31fc90bb86069f04b2468b9177cb93543f08 Mon Sep 17 00:00:00 2001 From: Ravishekhar Yadav Date: Thu, 16 Oct 2025 17:19:27 +0530 Subject: [PATCH 3/5] chore: XYNE-157 resolved comments --- frontend/src/components/ChatBox.tsx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 7ac9fe86f..04759b1c0 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -86,7 +86,6 @@ interface SelectedFile { uploadError?: string preview?: string // URL for image preview fileType?: FileType - } export const getFileIcon = (fileType: FileType | string | undefined) => { @@ -424,7 +423,7 @@ export const ChatBox = React.forwardRef( "citations", ) const [globalResults, setGlobalResults] = useState([]) - const fileAbortControllers = useRef>(new Map()); + const fileAbortControllers = useRef>(new Map()) // Unified function to enhance Google Sheets items with dummy "whole sheet" options const enhanceGoogleSheetsResults = useCallback( @@ -842,7 +841,6 @@ export const ChatBox = React.forwardRef( fileAbortControllers.current.set(file.id, new AbortController()) }) - // Set all files to uploading state setSelectedFiles((prev) => prev.map((f) => @@ -853,7 +851,9 @@ export const ChatBox = React.forwardRef( ) const uploadPromises = files.map(async (selectedFile) => { - const abortController = fileAbortControllers.current.get(selectedFile.id) + const abortController = fileAbortControllers.current.get( + selectedFile.id, + ) try { const formData = new FormData() formData.append("attachment", selectedFile.file) @@ -882,7 +882,7 @@ export const ChatBox = React.forwardRef( ...f, uploading: false, metadata, - abortController: undefined, + } : f, ), @@ -893,6 +893,18 @@ export const ChatBox = React.forwardRef( } } catch (error) { if (error instanceof Error && error.name === "AbortError") { + setSelectedFiles((prev) => + prev.map((f) => + f.id === selectedFile.id + ? { + ...f, + uploading: false, + uploadError: errorMessage, + + } + : f, + ), + ) return null } const errorMessage = @@ -904,7 +916,7 @@ export const ChatBox = React.forwardRef( ...f, uploading: false, uploadError: errorMessage, - abortController: undefined, + } : f, ), @@ -1006,9 +1018,9 @@ export const ChatBox = React.forwardRef( const removeFile = useCallback( async (id: string) => { const fileToRemove = selectedFiles.find((f) => f.id === id) - const abortController = fileAbortControllers.current.get(id); + const abortController = fileAbortControllers.current.get(id) if (fileToRemove?.uploading && abortController) { - abortController.abort(); + abortController.abort() } // If the file has metadata with fileId (meaning it's already uploaded), delete it from the server From 828568cb8e0282ec41477f4d6d7de0fedb0c5849 Mon Sep 17 00:00:00 2001 From: Ravishekhar Yadav Date: Thu, 16 Oct 2025 17:27:29 +0530 Subject: [PATCH 4/5] chore: XYNE-157 resolved comments --- frontend/src/components/ChatBox.tsx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 04759b1c0..8924040ea 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -882,7 +882,6 @@ export const ChatBox = React.forwardRef( ...f, uploading: false, metadata, - } : f, ), @@ -894,17 +893,16 @@ export const ChatBox = React.forwardRef( } catch (error) { if (error instanceof Error && error.name === "AbortError") { setSelectedFiles((prev) => - prev.map((f) => - f.id === selectedFile.id - ? { - ...f, - uploading: false, - uploadError: errorMessage, - - } - : f, - ), - ) + prev.map((f) => + f.id === selectedFile.id + ? { + ...f, + uploading: false, + uploadError: errorMessage, + } + : f, + ), + ) return null } const errorMessage = @@ -916,7 +914,6 @@ export const ChatBox = React.forwardRef( ...f, uploading: false, uploadError: errorMessage, - } : f, ), @@ -1021,6 +1018,7 @@ export const ChatBox = React.forwardRef( const abortController = fileAbortControllers.current.get(id) if (fileToRemove?.uploading && abortController) { abortController.abort() + fileAbortControllers.current.delete(id) } // If the file has metadata with fileId (meaning it's already uploaded), delete it from the server From 14710bdb69e8b13948143076f3d121ff46bd4f93 Mon Sep 17 00:00:00 2001 From: Ravishekhar Yadav Date: Fri, 17 Oct 2025 11:34:16 +0530 Subject: [PATCH 5/5] chore: XYNE-157 resolved comment --- frontend/src/components/ChatBox.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 8924040ea..e9354a04c 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -834,9 +834,7 @@ export const ChatBox = React.forwardRef( setUploadingFilesCount((prev) => prev + files.length) const uploadedMetadata: AttachmentMetadata[] = [] - // const fileAbortControllers = new Map( - // files.map((file) => [file.id, new AbortController()]), - // ) + files.forEach((file) => { fileAbortControllers.current.set(file.id, new AbortController()) })