Skip to content

Conversation

Ravishekhar7870
Copy link
Contributor

@Ravishekhar7870 Ravishekhar7870 commented Oct 10, 2025

Summary by CodeRabbit

  • New Features

    • File uploads now support per-file cancellation and progress feedback.
    • Cancel uploads directly from attachment previews or when closing the chat.
    • Improved UI states and icons reflect uploading, canceling, and errors, with clear notifications.
  • Bug Fixes

    • Prevents lingering uploads and background requests when navigating away or closing the chat.
    • Ensures consistent cleanup and state updates after cancellations or failures.
    • Aborted uploads terminate promptly, avoiding stalled operations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Implements cancellable file uploads end-to-end. Frontend adds per-file AbortControllers, consistent upload state/error handling, and UI hooks for cancel/removal. authFetch forwards AbortSignal to refresh-token requests. Server upload endpoints add early-abort checks and standardized 499 “Upload cancelled” responses across parsing and per-file processing.

Changes

Cohort / File(s) Summary
Frontend: Chat upload control and state
frontend/src/components/ChatBox.tsx
Adds per-file AbortControllers, encapsulated uploadFile with AbortSignal, consistent uploading/error cleanup, cancel-on-remove, teardown aborts, and UI wiring. Expands ChatBoxProps with user: PublicUser and optional agentIdFromChatData?: string | null.
Frontend: Auth fetch abort propagation
frontend/src/utils/authFetch.ts
Passes init?.signal into the refresh-token fetch, enabling refresh cancellation when original request is aborted. No API signature changes.
Server: Upload abort handling
server/api/files.ts
Adds request signal checks before/after form parsing and per-file processing; returns 499 on abort. Catch blocks map AbortError/aborted states to 499 “Upload cancelled.” Applies to attachments and file uploads.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant C as ChatBox (Frontend)
  participant AF as authFetch
  participant S as Server /api/files

  rect rgba(221,238,255,0.5)
    U->>C: Select files
    C->>C: Create AbortController per file
    C->>S: POST /files (signal)
    note over C,S: Upload with progress and per-file state
  end

  alt Upload succeeds
    S-->>C: 200 + file metadata
    C->>C: Update state (uploading=false, store metadata)
  else Upload fails
    S-->>C: 4xx/5xx error
    C->>C: Set uploadError, toast, cleanup controller
  else User cancels
    U-->>C: Click cancel/remove
    C->>C: controller.abort()
    C->>S: Abort signal
    S-->>C: 499 Upload cancelled
    C->>C: Mark uploading=false, cleanup
  end

  rect rgba(232,252,239,0.5)
    note over AF,S: Token refresh path
    C->>AF: fetch(..., signal)
    AF->>S: refresh-token (propagates signal)
    opt Aborted during refresh
      S-->>AF: Aborted/terminated
      AF-->>C: Propagate abort
      C->>C: Handle as cancellation
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • zereraz
  • shivamashtikar
  • junaid-shirur
  • devesh-juspay
  • kalpadhwaryu

Poem

I nibble bytes and hop through queues,
Cancelling uploads with gentle cues.
A twitch of whiskers—Abort! I say—
Server replies, “499 today.”
Fresh tokens fetched, or swiftly dropped,
My carrots compile, the spinners stopped.
Thump-thump: clean states, neatly mopped.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Bugfix cmgkqwns100001r2w9stk3a02” is not descriptive of the actual changes, which focus on adding upload cancellation, progress tracking, and abort handling across frontend and backend. It fails to convey the purpose or key features of this pull request. Please choose a concise, clear title that reflects the main changes, for example “Add upload cancellation and progress tracking to ChatBox and files API” so reviewers understand the intent at a glance.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix-cmgkqwns100001r2w9stk3a02

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Ravishekhar7870, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug by introducing comprehensive cancellation capabilities for file uploads. It integrates AbortController on the frontend to enable users to stop ongoing uploads and updates the backend to efficiently detect and respond to these cancellations, preventing unnecessary server-side processing. These changes enhance the overall responsiveness and user experience of the file upload feature.

Highlights

  • Upload Cancellation Mechanism: Implemented a robust cancellation mechanism for file uploads using AbortController on the frontend, allowing users to stop ongoing uploads.
  • Backend Abort Handling: The backend file upload endpoint (/api/v1/files/upload-attachment) now gracefully detects and handles aborted requests at various stages, returning a 499 status code.
  • Improved User Interface for Uploads: The UI for file removal buttons has been enhanced to visually indicate when an upload is in progress and to allow users to cancel it directly from the file list.
  • Cancellable Token Refresh: The authFetch utility now passes the AbortSignal to the token refresh request, making the refresh process also cancellable.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a file upload cancellation feature, which is a great addition. The implementation is solid, with changes on both the frontend to manage cancellation via AbortController and on the backend to handle aborted requests. I've identified a critical bug related to state management during cancellation that could lead to an inconsistent UI state. I've also included a few suggestions to improve performance, robustness, and code clarity. Overall, great work on this complex feature.

Comment on lines +1010 to +1015
setUploadControllers((prev) => {
const newMap = new Map(prev)
newMap.delete(id)
return newMap
})
setUploadingFilesCount((prev) => Math.max(0, prev - 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

These state updates are redundant and introduce a bug. When an upload is aborted via controller.abort(), the finally block in the uploadFile function is also triggered, which cleans up uploadControllers and decrements uploadingFilesCount. By calling the state setters here as well, uploadingFilesCount is decremented twice, leading to an incorrect count and potential UI bugs. The cleanup logic should be centralized in the uploadFile function to ensure it's the single source of truth.

Comment on lines 912 to 917
const uploadPromises = files.map(async (selectedFile) => {
try {
const formData = new FormData()
formData.append("attachment", selectedFile.file)
const response = await authFetch(
"/api/v1/files/upload-attachment",
{
method: "POST",
body: formData,
},
)

if (!response.ok) {
const error = await response.json()
throw new Error(error.message || "Upload failed")
}

const result = await response.json()
const metadata = result.attachments?.[0]

if (metadata) {
setSelectedFiles((prev) =>
prev.map((f) =>
f.id === selectedFile.id
? { ...f, uploading: false, metadata }
: f,
),
)
return metadata
} else {
throw new Error("No document ID returned from upload")
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Upload failed"
setSelectedFiles((prev) =>
prev.map((f) =>
f.id === selectedFile.id
? { ...f, uploading: false, uploadError: errorMessage }
: f,
),
)
toast.error({
title: "Upload failed",
description: `Failed to upload ${selectedFile.file.name}: ${errorMessage}`,
})
return null
} finally {
setUploadingFilesCount((prev) => prev - 1)
}
const controller = new AbortController()
setUploadControllers((prev) => new Map(prev).set(selectedFile.id, controller))

return uploadFile(selectedFile, controller.signal)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling a state setter (setUploadControllers) inside a .map() loop can trigger a state update for each file, which may lead to performance issues due to multiple re-renders. It's more efficient to batch these updates. You can create all the AbortController instances first, update the state once with all of them, and then map over the files to start the uploads.

const newControllers = new Map<string, AbortController>();
files.forEach((file) => newControllers.set(file.id, new AbortController()));
setUploadControllers((prev) => new Map([...prev, ...newControllers]));

const uploadPromises = files.map((selectedFile) => {
  const controller = newControllers.get(selectedFile.id)!;
  return uploadFile(selectedFile, controller.signal);
});

Comment on lines +2252 to +2257
// Abort any ongoing uploads
uploadControllers.forEach((controller) => {
controller.abort()
})
}
}, [])
}, [uploadControllers])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Aborting uploads on component unmount is good practice. However, this will cause the finally block in uploadFile to call state setters on an unmounted component, which will trigger a React warning in development: "Can't perform a React state update on an unmounted component."

To fix this, you can use a ref to track the component's mounted status.

  1. Add a ref to your component to track its mounted state:

    const mountedRef = React.useRef(true);
    React.useEffect(() => {
      return () => {
        mountedRef.current = false;
      };
    }, []);
  2. Then, guard the state updates in uploadFile's finally block:

    // inside uploadFile
    } finally {
      if (mountedRef.current) {
        setUploadControllers((prev) => {
          const newMap = new Map(prev);
          newMap.delete(selectedFile.id);
          return newMap;
        });
        setUploadingFilesCount((prev) => prev - 1);
      }
    }

Comment on lines +200 to +203
// Check if request was aborted
if (c.req.raw.signal?.aborted) {
return c.json({ error: 'Upload cancelled' }, 499)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This if (c.req.raw.signal?.aborted) check is repeated in multiple places within this function. To improve code clarity, reduce duplication, and make the cancellation logic easier to maintain, consider refactoring this into a reusable helper function.

For example, you could create a function that throws a specific error that your main try...catch block can handle:

const checkAborted = (c: Context) => {
  if (c.req.raw.signal?.aborted) {
    throw new HTTPException(499, { message: "Upload cancelled" });
  }
};

// Then in your handler:
try {
  checkAborted(c);
  const formData = await c.req.formData();
  checkAborted(c);
  // ...
} catch (error) {
  if (error instanceof HTTPException && error.status === 499) {
    return c.json({ error: error.message }, 499);
  }
  // ... other error handling
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/ChatBox.tsx (1)

2246-2258: Don’t abort active uploads on state updates

This effect depends on uploadControllers, so every time the map changes (e.g., when you add or remove a controller), the cleanup from the previous render runs and aborts all controllers captured in that closure. That means finishing one file or adding another cancels the rest of the ongoing uploads, defeating the whole feature.

Hold the controllers in a ref for cleanup and run the abort loop only on unmount, e.g.

-    useEffect(() => {
-      return () => {
-        const previewUrls = selectedFilesRef.current
-          .map((f) => f.preview)
-          .filter(Boolean) as string[]
-        cleanupPreviewUrls(previewUrls)
-
-        // Abort any ongoing uploads
-        uploadControllers.forEach((controller) => {
-          controller.abort()
-        })
-      }
-    }, [uploadControllers])
+    const uploadControllersRef = useRef(uploadControllers)
+    useEffect(() => {
+      uploadControllersRef.current = uploadControllers
+    }, [uploadControllers])
+
+    useEffect(() => {
+      return () => {
+        const previewUrls = selectedFilesRef.current
+          .map((f) => f.preview)
+          .filter(Boolean) as string[]
+        cleanupPreviewUrls(previewUrls)
+
+        uploadControllersRef.current.forEach((controller) => {
+          controller.abort()
+        })
+      }
+    }, [])

This preserves the unmount cleanup without killing active uploads on every state change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f1ec904 and a3a33c7.

📒 Files selected for processing (3)
  • frontend/src/components/ChatBox.tsx (9 hunks)
  • frontend/src/utils/authFetch.ts (1 hunks)
  • server/api/files.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/ChatBox.tsx (2)
frontend/src/components/ClFileUpload.tsx (1)
  • SelectedFile (8-12)
frontend/src/utils/authFetch.ts (1)
  • authFetch (4-28)
🪛 GitHub Actions: TypeScript Build Check
server/api/files.ts

[error] 202-202: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.


[error] 220-220: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.


[error] 243-243: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.


[error] 258-258: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.


[error] 422-422: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.

Comment on lines +885 to +891
setUploadControllers((prev) => {
const newMap = new Map(prev)
newMap.delete(selectedFile.id)
return newMap
})
setUploadingFilesCount((prev) => prev - 1)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent double decrement of uploading count

We’re decrementing uploadingFilesCount twice for the same upload: once in removeFile (Lines 1007-1016) and again in uploadFile’s finally (Lines 885-891). When a user cancels an upload, the manual decrement drops the counter immediately, and the finally runs shortly after and knocks it down again. With multiple files, this enables “Send” while other uploads are still active and can even drive the counter negative, breaking the uploadCompleteResolver logic.

Remove the manual decrement from removeFile and clamp the finally decrement so it only runs once per upload:

-      if (fileToRemove?.uploading) {
-        const controller = uploadControllers.get(id)
-        controller?.abort()
-        setUploadControllers((prev) => {
-          const newMap = new Map(prev)
-          newMap.delete(id)
-          return newMap
-        })
-        setUploadingFilesCount((prev) => Math.max(0, prev - 1))
-      }
+      if (fileToRemove?.uploading) {
+        const controller = uploadControllers.get(id)
+        controller?.abort()
+        setUploadControllers((prev) => {
+          const newMap = new Map(prev)
+          newMap.delete(id)
+          return newMap
+        })
+      }
...
-          setUploadingFilesCount((prev) => prev - 1)
+          setUploadingFilesCount((prev) => Math.max(0, prev - 1))

This keeps the counter accurate and the send button correctly disabled.

Also applies to: 1007-1016

🤖 Prompt for AI Agents
In frontend/src/components/ChatBox.tsx around lines 885-891 (and also apply
change to 1007-1016), remove the manual setUploadingFilesCount((prev) => prev -
1) call from removeFile, and instead ensure the finally block in uploadFile
performs an atomic check-and-decrement: update setUploadControllers via its
functional updater to detect whether the selectedFile.id is present in the map,
delete it only if present, and only then decrement setUploadingFilesCount once;
if the id is already gone, do not decrement. This guarantees the counter is
decremented exactly once per upload and prevents negative or double decrements.

Comment on lines +200 to +203
// Check if request was aborted
if (c.req.raw.signal?.aborted) {
return c.json({ error: 'Upload cancelled' }, 499)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix 499 status type mismatch

The TypeScript build is failing (TS2769 in the pipeline log) because Hono’s c.json second argument is typed as ContentfulStatusCode, which does not include 499. Every return c.json(..., 499) here (Lines 200, 219, 243, 258, and 421) breaks the build. Please either switch to a status that’s part of Hono’s union or construct the response manually, e.g.

-      return c.json({ error: 'Upload cancelled' }, 499)
+      return new Response(JSON.stringify({ error: "Upload cancelled" }), {
+        status: 499,
+        headers: { "content-type": "application/json" },
+      })

and mirror that change in the other call sites so the build passes while still emitting 499.

Also applies to: 219-221, 242-244, 257-259, 421-423

🧰 Tools
🪛 GitHub Actions: TypeScript Build Check

[error] 202-202: TS2769: No overload matches this call. Argument of type '499' is not assignable to parameter of type 'ContentfulStatusCode | undefined'.

🤖 Prompt for AI Agents
In server/api/files.ts around lines 200-203 (and similarly at 219-221, 242-244,
257-259, 421-423), the code returns c.json(..., 499) but Hono's c.json second
argument type doesn't include 499; replace those calls with a manually
constructed Response that sets status to 499 and content-type to
application/json, e.g. return new Response(JSON.stringify({ error: 'Upload
cancelled' }), { status: 499, headers: { 'Content-Type': 'application/json' }
}); apply the same replacement at each listed call site so the build type error
is resolved while preserving the 499 status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant