fix(frontend): retain presented artifacts in header dropdown (#3854)

* fix(frontend): retain presented artifacts in dropdown

* refactor(agents): trim file editing prompt context

* Revert "refactor(agents): trim file editing prompt context"

This reverts commit cad105265024335222e374692dde37424d11024b.
This commit is contained in:
Huixin615 2026-06-28 23:30:40 +08:00 committed by GitHub
parent 6d7b94c5f8
commit b3c312b795
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 12 deletions

View File

@ -84,13 +84,41 @@ export function ArtifactFileDetail({
}
return filepathFromProps;
}, [filepathFromProps, isWriteFile]);
const artifactOptions = useMemo(() => {
const list = artifacts ?? [];
if (list.includes(filepath)) {
return list;
// Keep these local because ChatBox replaces context artifacts with thread state.
const [openedPresentedFilepaths, setOpenedPresentedFilepaths] = useState<
string[]
>(() => {
if (isWriteFile || artifacts.includes(filepath)) {
return [];
}
return [filepath, ...list];
}, [artifacts, filepath]);
return [filepath];
});
useEffect(() => {
if (isWriteFile || artifacts.includes(filepath)) {
return;
}
setOpenedPresentedFilepaths((current) => {
if (current.includes(filepath)) {
return current;
}
return [...current, filepath];
});
}, [artifacts, filepath, isWriteFile]);
const artifactOptions = useMemo(() => {
if (isWriteFile) {
return artifacts;
}
const currentIsPresented = !artifacts.includes(filepath);
const presentedFilepaths =
currentIsPresented && !openedPresentedFilepaths.includes(filepath)
? [...openedPresentedFilepaths, filepath]
: openedPresentedFilepaths;
const presentedSet = new Set(presentedFilepaths);
return [
...presentedFilepaths,
...artifacts.filter((artifact) => !presentedSet.has(artifact)),
];
}, [artifacts, filepath, isWriteFile, openedPresentedFilepaths]);
const isSkillFile = useMemo(() => {
return filepath.endsWith(".skill");
}, [filepath]);

View File

@ -262,16 +262,16 @@ test.describe("Artifact preview stability", () => {
).toBeVisible();
});
test("shows the title for a presented artifact missing from thread artifacts", async ({
test("keeps an opened presented artifact in the header dropdown", async ({
page,
}) => {
mockLangGraphAPI(page, {
threads: [
{
thread_id: PRESENTED_THREAD_ID,
title: "Presented artifact title fallback",
title: "Presented artifact dropdown history",
messages: presentFilesMessages(),
artifacts: [],
artifacts: [MARKDOWN_ARTIFACT_PATH],
},
],
});
@ -284,6 +284,15 @@ test.describe("Artifact preview stability", () => {
body: "# Presented Report\n\nGenerated content",
}),
);
await page.route(
`**/api/threads/${PRESENTED_THREAD_ID}/artifacts/artifact-fixtures/report.md`,
(route) =>
route.fulfill({
status: 200,
contentType: "text/markdown",
body: "# Thread Report\n\nTracked artifact content",
}),
);
await page.goto(`/workspace/chats/${PRESENTED_THREAD_ID}`);
@ -295,11 +304,20 @@ test.describe("Artifact preview stability", () => {
const artifactsPanel = page.locator("#artifacts");
// 1. Header title should fall back to the current filepath even though
// thread.values.artifacts is empty.
await expect(artifactsPanel.getByText("presented-report.md")).toBeVisible();
await expect(artifactsPanel.getByText("Presented Report")).toBeVisible();
// 2. Preview content should still render correctly.
const artifactSelect = artifactsPanel.getByRole("combobox");
await artifactSelect.click();
await page.getByRole("option", { name: "report.md", exact: true }).click();
await expect(artifactsPanel.getByText("Thread Report")).toBeVisible();
await artifactSelect.click();
const presentedOption = page.getByRole("option", {
name: "presented-report.md",
});
await expect(presentedOption).toBeVisible();
await presentedOption.click();
await expect(artifactsPanel.getByText("Presented Report")).toBeVisible();
});
});