fix(chat): handle empty uploaded files case and improve artifact selection logic (#979)

* fix(chat): handle empty uploaded files case and improve artifact selection logic

* Update frontend/src/components/workspace/chats/chat-box.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: address code review suggestions from PR #979 (#3)

* Initial plan

* fix: address PR #979 review suggestions

- utils.ts: scope (empty) check inside <uploaded_files> tag content
- chat-box.tsx: remove stale `artifacts` from useEffect deps
- context.tsx: wrap select/deselect with useCallback for stable refs
- test: add test_empty_new_files_produces_empty_marker

Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: foreleven <4785594+foreleven@users.noreply.github.com>
This commit is contained in:
JeffJiang
2026-03-05 17:45:25 +08:00
committed by GitHub
parent b17c087174
commit 1b3939cb78
5 changed files with 51 additions and 18 deletions

View File

@@ -53,12 +53,15 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
lines.append("The following files were uploaded in this message:")
lines.append("")
for file in new_files:
size_kb = file["size"] / 1024
size_str = f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
lines.append(f"- {file['filename']} ({size_str})")
lines.append(f" Path: {file['path']}")
lines.append("")
if new_files:
for file in new_files:
size_kb = file["size"] / 1024
size_str = f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
lines.append(f"- {file['filename']} ({size_str})")
lines.append(f" Path: {file['path']}")
lines.append("")
else:
lines.append("(empty)")
if historical_files:
lines.append("The following files were uploaded in previous messages and are still available:")

View File

@@ -188,6 +188,13 @@ class TestCreateFilesMessage:
msg = mw._create_files_message([self._new_file()], [])
assert "read_file" in msg
def test_empty_new_files_produces_empty_marker(self, tmp_path):
mw = _middleware(tmp_path)
msg = mw._create_files_message([], [])
assert "(empty)" in msg
assert "<uploaded_files>" in msg
assert "</uploaded_files>" in msg
# ---------------------------------------------------------------------------
# before_agent

View File

@@ -1,4 +1,10 @@
import { createContext, useContext, useState, type ReactNode } from "react";
import {
createContext,
useCallback,
useContext,
useState,
type ReactNode,
} from "react";
import { useSidebar } from "@/components/ui/sidebar";
import { env } from "@/env";
@@ -35,20 +41,23 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) {
const [autoOpen, setAutoOpen] = useState(true);
const { setOpen: setSidebarOpen } = useSidebar();
const select = (artifact: string, autoSelect = false) => {
setSelectedArtifact(artifact);
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true") {
setSidebarOpen(false);
}
if (!autoSelect) {
setAutoSelect(false);
}
};
const select = useCallback(
(artifact: string, autoSelect = false) => {
setSelectedArtifact(artifact);
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true") {
setSidebarOpen(false);
}
if (!autoSelect) {
setAutoSelect(false);
}
},
[setSidebarOpen, setSelectedArtifact, setAutoSelect],
);
const deselect = () => {
const deselect = useCallback(() => {
setSelectedArtifact(null);
setAutoSelect(true);
};
}, []);
const value: ArtifactsContextType = {
artifacts,

View File

@@ -34,12 +34,19 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
setOpen: setArtifactsOpen,
setArtifacts,
select: selectArtifact,
deselect,
selectedArtifact,
} = useArtifacts();
const [autoSelectFirstArtifact, setAutoSelectFirstArtifact] = useState(true);
useEffect(() => {
setArtifacts(thread.values.artifacts);
if (
thread.values.artifacts?.length === 0 ||
(selectedArtifact && !thread.values.artifacts?.includes(selectedArtifact))
) {
deselect();
}
if (
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&
autoSelectFirstArtifact
@@ -51,7 +58,9 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
}
}, [
autoSelectFirstArtifact,
deselect,
selectArtifact,
selectedArtifact,
setArtifacts,
thread.values.artifacts,
]);

View File

@@ -300,6 +300,11 @@ export function parseUploadedFiles(content: string): FileInMessage[] {
return [];
}
// Check if the backend reported no new files were uploaded in this message
if (uploadedFilesContent?.includes("(empty)")) {
return [];
}
// Parse file list
// Format: - filename (size)\n Path: /path/to/file
const fileRegex = /- ([^\n(]+)\s*\(([^)]+)\)\s*\n\s*Path:\s*([^\n]+)/g;