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

## 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):

```typescript
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:

```typescript
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.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: ximi <octo-patch@github.com>
This commit is contained in:
Octopus
2026-05-15 09:53:35 +08:00
committed by GitHub
parent 58819f5d3e
commit d887b578c5

View File

@@ -58,7 +58,23 @@ export function SingleChatBox({
useEffect(() => {
const messages = conversation?.messages;
if (Array.isArray(messages)) {
setDerivedMessages(messages);
setDerivedMessages((prevMessages) => {
// Preserve uploaded file objects from local state that the server doesn't
// persist (e.g. File instances). Build a map of message id → files from
// the current local state so they survive when server data is applied.
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]);