mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-05-02 02:00:45 +08:00
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:
@@ -53,12 +53,15 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]):
|
|||||||
|
|
||||||
lines.append("The following files were uploaded in this message:")
|
lines.append("The following files were uploaded in this message:")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
for file in new_files:
|
if new_files:
|
||||||
size_kb = file["size"] / 1024
|
for file in new_files:
|
||||||
size_str = f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
|
size_kb = file["size"] / 1024
|
||||||
lines.append(f"- {file['filename']} ({size_str})")
|
size_str = f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
|
||||||
lines.append(f" Path: {file['path']}")
|
lines.append(f"- {file['filename']} ({size_str})")
|
||||||
lines.append("")
|
lines.append(f" Path: {file['path']}")
|
||||||
|
lines.append("")
|
||||||
|
else:
|
||||||
|
lines.append("(empty)")
|
||||||
|
|
||||||
if historical_files:
|
if historical_files:
|
||||||
lines.append("The following files were uploaded in previous messages and are still available:")
|
lines.append("The following files were uploaded in previous messages and are still available:")
|
||||||
|
|||||||
@@ -188,6 +188,13 @@ class TestCreateFilesMessage:
|
|||||||
msg = mw._create_files_message([self._new_file()], [])
|
msg = mw._create_files_message([self._new_file()], [])
|
||||||
assert "read_file" in msg
|
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
|
# before_agent
|
||||||
|
|||||||
@@ -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 { useSidebar } from "@/components/ui/sidebar";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
@@ -35,20 +41,23 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) {
|
|||||||
const [autoOpen, setAutoOpen] = useState(true);
|
const [autoOpen, setAutoOpen] = useState(true);
|
||||||
const { setOpen: setSidebarOpen } = useSidebar();
|
const { setOpen: setSidebarOpen } = useSidebar();
|
||||||
|
|
||||||
const select = (artifact: string, autoSelect = false) => {
|
const select = useCallback(
|
||||||
setSelectedArtifact(artifact);
|
(artifact: string, autoSelect = false) => {
|
||||||
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true") {
|
setSelectedArtifact(artifact);
|
||||||
setSidebarOpen(false);
|
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true") {
|
||||||
}
|
setSidebarOpen(false);
|
||||||
if (!autoSelect) {
|
}
|
||||||
setAutoSelect(false);
|
if (!autoSelect) {
|
||||||
}
|
setAutoSelect(false);
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[setSidebarOpen, setSelectedArtifact, setAutoSelect],
|
||||||
|
);
|
||||||
|
|
||||||
const deselect = () => {
|
const deselect = useCallback(() => {
|
||||||
setSelectedArtifact(null);
|
setSelectedArtifact(null);
|
||||||
setAutoSelect(true);
|
setAutoSelect(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const value: ArtifactsContextType = {
|
const value: ArtifactsContextType = {
|
||||||
artifacts,
|
artifacts,
|
||||||
|
|||||||
@@ -34,12 +34,19 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
|||||||
setOpen: setArtifactsOpen,
|
setOpen: setArtifactsOpen,
|
||||||
setArtifacts,
|
setArtifacts,
|
||||||
select: selectArtifact,
|
select: selectArtifact,
|
||||||
|
deselect,
|
||||||
selectedArtifact,
|
selectedArtifact,
|
||||||
} = useArtifacts();
|
} = useArtifacts();
|
||||||
|
|
||||||
const [autoSelectFirstArtifact, setAutoSelectFirstArtifact] = useState(true);
|
const [autoSelectFirstArtifact, setAutoSelectFirstArtifact] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setArtifacts(thread.values.artifacts);
|
setArtifacts(thread.values.artifacts);
|
||||||
|
if (
|
||||||
|
thread.values.artifacts?.length === 0 ||
|
||||||
|
(selectedArtifact && !thread.values.artifacts?.includes(selectedArtifact))
|
||||||
|
) {
|
||||||
|
deselect();
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&
|
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&
|
||||||
autoSelectFirstArtifact
|
autoSelectFirstArtifact
|
||||||
@@ -51,7 +58,9 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
|||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
autoSelectFirstArtifact,
|
autoSelectFirstArtifact,
|
||||||
|
deselect,
|
||||||
selectArtifact,
|
selectArtifact,
|
||||||
|
selectedArtifact,
|
||||||
setArtifacts,
|
setArtifacts,
|
||||||
thread.values.artifacts,
|
thread.values.artifacts,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -300,6 +300,11 @@ export function parseUploadedFiles(content: string): FileInMessage[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the backend reported no new files were uploaded in this message
|
||||||
|
if (uploadedFilesContent?.includes("(empty)")) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// Parse file list
|
// Parse file list
|
||||||
// Format: - filename (size)\n Path: /path/to/file
|
// Format: - filename (size)\n Path: /path/to/file
|
||||||
const fileRegex = /- ([^\n(]+)\s*\(([^)]+)\)\s*\n\s*Path:\s*([^\n]+)/g;
|
const fileRegex = /- ([^\n(]+)\s*\(([^)]+)\)\s*\n\s*Path:\s*([^\n]+)/g;
|
||||||
|
|||||||
Reference in New Issue
Block a user