Skip to content

fix: preserve uploaded file attachments after subsequent assistant messages#13993

Open
octo-patch wants to merge 3 commits intoinfiniflow:mainfrom
octo-patch:fix/issue-13959-preserve-uploaded-file-display
Open

fix: preserve uploaded file attachments after subsequent assistant messages#13993
octo-patch wants to merge 3 commits intoinfiniflow:mainfrom
octo-patch:fix/issue-13959-preserve-uploaded-file-display

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

@octo-patch octo-patch commented Apr 9, 2026

Problem

When a user uploads a file attachment in their first message (Q1) and then sends a follow-up message (Q2) that triggers a backend response, the uploaded file attachment disappears from Q1 in the chat UI.

Fixes #13959

Root Cause

In single-chat-box.tsx, a useEffect hook syncs derivedMessages from conversation?.messages whenever the conversation data changes (e.g., after a new assistant reply arrives):

useEffect(() => {
  const messages = conversation?.messages;
  if (Array.isArray(messages)) {
    setDerivedMessages(messages); // ← overwrites local state
  }
}, [conversation?.messages, setDerivedMessages]);

The problem is that conversation.messages comes from the server, which stores messages as plain JSON. Browser File objects (uploaded by the user) cannot be serialized to JSON, so they are never stored on the server. Each time the server data is applied to local state, the files array on the user's first message is lost.

Fix

Instead of replacing the local messages wholesale, preserve any files entries from the previous local state by ID before applying the server data:

useEffect(() => {
  const messages = conversation?.messages;
  if (Array.isArray(messages)) {
    setDerivedMessages((prevMessages) => {
      const filesMap = new Map(
        prevMessages
          .filter((m) => m.files?.length)
          .map((m) => [m.id, m.files]),
      );
      if (filesMap.size === 0) {
        return messages;
      }
      return messages.map((m) => ({
        ...m,
        files: filesMap.get(m.id) ?? m.files,
      }));
    });
  }
}, [conversation?.messages, setDerivedMessages]);

This is a minimal, targeted fix: when there are no local files to preserve the behavior is identical to before (early return with plain assignment). When local file objects exist they are re-attached to the corresponding server messages by ID.

Summary by CodeRabbit

  • Bug Fixes

    • Improved search query processing to properly handle special characters and apostrophes in search terms and synonyms.
    • Fixed chat message file attachments to persist when syncing with server.
  • Refactor

    • Simplified OCR detection return values by removing timing metadata.

octo-patch and others added 3 commits April 7, 2026 11:39
…ssages

When a user uploads a file in Q1 and then sends a KB-only Q2, the server
response resets derivedMessages from conversation?.messages which lacks the
local File objects attached to Q1. The attachment icon therefore disappeared
from the earlier message.

Fix: when syncing server messages to local state, build a map of message-id
to files from the previous local state and restore those files onto the
matching server messages, so uploaded attachments remain visible throughout
the conversation.

Fixes infiniflow#13959
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

The PR modifies message state synchronization in the chat UI to preserve locally cached file attachments when syncing with server messages, preventing attachments from disappearing during subsequent message updates. Additionally, three query-related modules add single-quote character stripping as a preprocessing step, and the OCR detection method removes timing metadata from its return value.

Changes

Cohort / File(s) Summary
Chat Attachment Preservation
web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx
Updated useEffect to preserve locally cached files from existing messages when syncing derivedMessages with server data, using functional state updates to merge local attachment state with incoming server messages.
Query Preprocessing
common/query_base.py, memory/services/query.py, rag/nlp/query.py
Added single-quote character stripping from tokenized synonym strings and special-character preprocessing to prevent quote propagation into downstream query construction and lexer operations.
OCR Method Simplification
deepdoc/vision/ocr.py
Removed timing metadata (time_dict) from detect() return value; method now returns dt_boxes|None instead of tuple with elapsed time information.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Attachments cling through syncs with care,
Quotes are stripped from thin air,
Files preserved, no more they fade,
When knowledge queries are made!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes in common/query_base.py, deepdoc/vision/ocr.py, memory/services/query.py, and rag/nlp/query.py appear unrelated to the #13959 file attachment preservation fix and should be excluded. Remove the four unrelated files (query_base.py, ocr.py, services/query.py, rag/nlp/query.py) that fix single-quote handling and timing issues, as they are out of scope for the file attachment preservation issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: preserving uploaded file attachments after assistant messages in the chat UI.
Description check ✅ Passed The description covers all required template sections with clear problem statement, root cause analysis, and detailed fix explanation.
Linked Issues check ✅ Passed The code changes in single-chat-box.tsx directly address issue #13959 by implementing the file preservation fix that prevents uploaded attachments from disappearing after subsequent assistant messages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@dosubot dosubot bot added the 🐞 bug Something isn't working, pull request that fix bug. label Apr 9, 2026
Copy link
Copy Markdown
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx`:
- Around line 65-76: The files restoration logic builds a Map keyed by m.id
which can collide across messages with the same id; change the map to use
buildMessageUuidWithRole(m.id, m.role) when creating filesMap from prevMessages
and likewise use buildMessageUuidWithRole(m.id, m.role) when reading from
filesMap in the return messages.map callback so that files are matched by
id+role (refer to filesMap, prevMessages, messages, and
buildMessageUuidWithRole).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a66ed4d-75ff-4feb-ac86-eb88dfa5c536

📥 Commits

Reviewing files that changed from the base of the PR and between c13f885 and 4aa01c4.

📒 Files selected for processing (5)
  • common/query_base.py
  • deepdoc/vision/ocr.py
  • memory/services/query.py
  • rag/nlp/query.py
  • web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx

Comment on lines +65 to +76
const filesMap = new Map(
prevMessages
.filter((m) => m.files?.length)
.map((m) => [m.id, m.files]),
);
if (filesMap.size === 0) {
return messages;
}
return messages.map((m) => ({
...m,
files: filesMap.get(m.id) ?? m.files,
}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use a role-qualified key when restoring files to avoid cross-message collisions.

On Line 65 and Line 75, keying by m.id alone can mis-attach files if multiple messages share the same id (the component already treats id+role as identity at Line 102). Use buildMessageUuidWithRole for both map write/read.

🔧 Proposed fix
-        const filesMap = new Map(
+        const filesMap = new Map(
           prevMessages
             .filter((m) => m.files?.length)
-            .map((m) => [m.id, m.files]),
+            .map((m) => [buildMessageUuidWithRole(m), m.files]),
         );
@@
         return messages.map((m) => ({
           ...m,
-          files: filesMap.get(m.id) ?? m.files,
+          files: filesMap.get(buildMessageUuidWithRole(m)) ?? m.files,
         }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const filesMap = new Map(
prevMessages
.filter((m) => m.files?.length)
.map((m) => [m.id, m.files]),
);
if (filesMap.size === 0) {
return messages;
}
return messages.map((m) => ({
...m,
files: filesMap.get(m.id) ?? m.files,
}));
const filesMap = new Map(
prevMessages
.filter((m) => m.files?.length)
.map((m) => [buildMessageUuidWithRole(m), m.files]),
);
if (filesMap.size === 0) {
return messages;
}
return messages.map((m) => ({
...m,
files: filesMap.get(buildMessageUuidWithRole(m)) ?? m.files,
}));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx` around lines 65 -
76, The files restoration logic builds a Map keyed by m.id which can collide
across messages with the same id; change the map to use
buildMessageUuidWithRole(m.id, m.role) when creating filesMap from prevMessages
and likewise use buildMessageUuidWithRole(m.id, m.role) when reading from
filesMap in the return messages.map callback so that files are matched by
id+role (refer to filesMap, prevMessages, messages, and
buildMessageUuidWithRole).

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

Labels

🐞 bug Something isn't working, pull request that fix bug. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Uploaded chat attachment disappears from previous message after a later KB-only question

1 participant