mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(frontend): keep artifact lists scrollable (#7284)
Use the shared ScrollArea so long artifact lists remain reachable in desktop panels and mobile drawers. Add browser coverage for short and long lists before clicking the final file. Adapt the ScrollArea approach from LeonSGP43 in bytedance/deer-flow#2076 and verify it against current main for issue #2074.
This commit is contained in:
parent
a30b362e9c
commit
db7f919118
@ -2930,7 +2930,7 @@ recording the others. A concurrent scratch-file removal therefore does not
|
||||
discard the whole snapshot or suppress detection of other produced outputs.
|
||||
This remains a best-effort scan, not an atomic filesystem snapshot.
|
||||
|
||||
Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. When a completed response successfully presents between 2 and 50 files, its final file card also offers one ZIP download. Archive membership comes from the terminal delivery receipt rather than browser-supplied paths, and the ZIP contains the current file versions, which may have changed since the response. The currently selected formal artifact is refreshed once when the run finishes so edits become visible without a manual reload. Existing UTF-8 text artifacts under `/mnt/user-data/outputs` can also be edited and explicitly saved from the panel on Unix and Windows while the thread is idle; saves use content revisions to prevent overwriting agent changes. Source previews also recognize extensionless `Dockerfile` and `Makefile` artifacts by their file names. Unknown file types, including names such as `constructor` and `__proto__`, retain the download fallback.
|
||||
Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. Long artifact lists scroll within the panel on desktop and in the mobile drawer, keeping every file reachable. When a completed response successfully presents between 2 and 50 files, its final file card also offers one ZIP download. Archive membership comes from the terminal delivery receipt rather than browser-supplied paths, and the ZIP contains the current file versions, which may have changed since the response. The currently selected formal artifact is refreshed once when the run finishes so edits become visible without a manual reload. Existing UTF-8 text artifacts under `/mnt/user-data/outputs` can also be edited and explicitly saved from the panel on Unix and Windows while the thread is idle; saves use content revisions to prevent overwriting agent changes. Source previews also recognize extensionless `Dockerfile` and `Makefile` artifacts by their file names. Unknown file types, including names such as `constructor` and `__proto__`, retain the download fallback.
|
||||
|
||||
Unsaved artifact edits survive switching chats, workspace pages, or visiting the
|
||||
home page and returning in the same browser tab. Drafts stay in memory for the
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@ -406,13 +407,13 @@ const ChatBoxContent: React.FC<ChatBoxProps> = ({
|
||||
<header className="shrink-0">
|
||||
<h2 className="text-lg font-medium">Artifacts</h2>
|
||||
</header>
|
||||
<main className="min-h-0 grow">
|
||||
<ScrollArea className="min-h-0 grow">
|
||||
<ArtifactFileList
|
||||
className="max-w-(--container-width-sm) p-4 pt-12"
|
||||
files={artifacts}
|
||||
threadId={threadId}
|
||||
/>
|
||||
</main>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
84
frontend/tests/e2e/artifact-list-scroll.spec.ts
Normal file
84
frontend/tests/e2e/artifact-list-scroll.spec.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { mockLangGraphAPI } from "./utils/mock-api";
|
||||
|
||||
const THREAD_ID = "00000000-0000-0000-0000-000000002074";
|
||||
|
||||
for (const viewport of [
|
||||
{ name: "desktop", width: 1280, height: 720 },
|
||||
{ name: "narrow", width: 390, height: 844 },
|
||||
]) {
|
||||
test.describe(`Artifact list (${viewport.name})`, () => {
|
||||
test.use({ viewport });
|
||||
|
||||
for (const count of [1, 30]) {
|
||||
test(`can reach and open the last of ${count} files`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const artifacts = Array.from(
|
||||
{ length: count },
|
||||
(_, index) => `/mnt/user-data/outputs/report-${index + 1}.txt`,
|
||||
);
|
||||
const lastPath = artifacts[artifacts.length - 1]!;
|
||||
const content = `Contents of report ${count}`;
|
||||
mockLangGraphAPI(page, {
|
||||
threads: [
|
||||
{
|
||||
thread_id: THREAD_ID,
|
||||
title: "Artifact list scrolling",
|
||||
artifacts,
|
||||
messages: [
|
||||
{ type: "human", id: "human", content: "Create the reports" },
|
||||
{ type: "ai", id: "ai", content: "The reports are ready." },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
await page.route(
|
||||
`**/api/threads/${THREAD_ID}/artifacts${lastPath}`,
|
||||
(route) =>
|
||||
route.fulfill({ contentType: "text/plain", body: content }),
|
||||
);
|
||||
await page.goto(`/workspace/chats/${THREAD_ID}`);
|
||||
await page.getByTestId("artifact-trigger").click();
|
||||
|
||||
const panel =
|
||||
viewport.name === "desktop"
|
||||
? page.locator("#artifacts")
|
||||
: page.getByRole("dialog", { name: "Artifacts", exact: true });
|
||||
const firstFile = panel.getByText("report-1.txt", { exact: true });
|
||||
const lastFile = panel.getByText(`report-${count}.txt`, {
|
||||
exact: true,
|
||||
});
|
||||
await expect(firstFile).toBeInViewport({ ratio: 1 });
|
||||
if (count > 1) {
|
||||
await expect(lastFile).not.toBeInViewport();
|
||||
}
|
||||
await firstFile.hover();
|
||||
await page.mouse.wheel(0, 10_000);
|
||||
|
||||
try {
|
||||
// Check reachability before click(), which would auto-scroll and
|
||||
// hide the missing user-scrollable region on the old layout.
|
||||
await expect(lastFile).toBeInViewport({ ratio: 1 });
|
||||
await expect(
|
||||
panel
|
||||
.getByRole("heading", { name: "Artifacts", exact: true })
|
||||
.last(),
|
||||
).toBeInViewport({ ratio: 1 });
|
||||
} finally {
|
||||
const screenshot = testInfo.outputPath(
|
||||
"artifact-list-after-scroll.png",
|
||||
);
|
||||
await page.screenshot({ path: screenshot });
|
||||
await testInfo.attach("artifact-list-after-scroll", {
|
||||
path: screenshot,
|
||||
contentType: "image/png",
|
||||
});
|
||||
}
|
||||
await lastFile.click();
|
||||
await expect(panel.locator(".cm-content")).toHaveText(content);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user