mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-09 16:08:31 +00:00
fix(frontend): show filename for presented artifacts missing from thread.values.artifacts (#3198)
Closes #3192 Root cause ---------- The artifact preview header is driven by a Radix Select whose <SelectValue> renders the label of the <SelectItem> matching the current value. The option list was built solely from `artifacts` in ArtifactsContext, which is only synced from `thread.values.artifacts` (chat-box.tsx). Artifacts surfaced via the message-layer `present_files` tool call are never written back to `thread.values.artifacts`, so when such a file is selected its filepath has no matching <SelectItem>. Radix then renders an empty trigger and the header filename appears blank, even though the preview body loads correctly. Fix --- Compute `artifactOptions` as a defensive union: if the currently selected filepath is missing from `artifacts`, prepend it so a matching <SelectItem> always exists. This keeps the header label in sync with the active file without changing context semantics or coupling the UI to a future auto-discovery mechanism (see existing TODO in chat-box.tsx). Tests ----- Add an e2e case that mocks a thread whose only artifact is delivered via `present_files` (thread.values.artifacts = []) and asserts both the header title and preview content render. All 21 e2e tests pass.
This commit is contained in:
parent
7c8a17c650
commit
3e2f1bbe49
2 changed files with 77 additions and 3 deletions
|
|
@ -84,6 +84,13 @@ export function ArtifactFileDetail({
|
|||
}
|
||||
return filepathFromProps;
|
||||
}, [filepathFromProps, isWriteFile]);
|
||||
const artifactOptions = useMemo(() => {
|
||||
const list = artifacts ?? [];
|
||||
if (list.includes(filepath)) {
|
||||
return list;
|
||||
}
|
||||
return [filepath, ...list];
|
||||
}, [artifacts, filepath]);
|
||||
const isSkillFile = useMemo(() => {
|
||||
return filepath.endsWith(".skill");
|
||||
}, [filepath]);
|
||||
|
|
@ -178,9 +185,9 @@ export function ArtifactFileDetail({
|
|||
</SelectTrigger>
|
||||
<SelectContent className="select-none">
|
||||
<SelectGroup>
|
||||
{(artifacts ?? []).map((filepath) => (
|
||||
<SelectItem key={filepath} value={filepath}>
|
||||
{getFileName(filepath)}
|
||||
{artifactOptions.map((option) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{getFileName(option)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import { mockLangGraphAPI } from "./utils/mock-api";
|
|||
const ARTIFACT_PATH = "/artifact-fixtures/report.html";
|
||||
const MARKDOWN_ARTIFACT_PATH = "/artifact-fixtures/report.md";
|
||||
const JSON_ARTIFACT_PATH = "/artifact-fixtures/report.json";
|
||||
const PRESENTED_ARTIFACT_PATH = "/mnt/user-data/outputs/presented-report.md";
|
||||
const IN_PROGRESS_THREAD_ID = "00000000-0000-0000-0000-000000003119";
|
||||
const COMPLETE_THREAD_ID = "00000000-0000-0000-0000-000000003120";
|
||||
const MARKDOWN_THREAD_ID = "00000000-0000-0000-0000-000000003121";
|
||||
const MARKDOWN_ANCHOR_THREAD_ID = "00000000-0000-0000-0000-000000003123";
|
||||
const JSON_THREAD_ID = "00000000-0000-0000-0000-000000003122";
|
||||
const PRESENTED_THREAD_ID = "00000000-0000-0000-0000-000000003123";
|
||||
|
||||
function writeFileMessages({
|
||||
path = ARTIFACT_PATH,
|
||||
|
|
@ -57,6 +59,30 @@ function writeFileMessages({
|
|||
return messages;
|
||||
}
|
||||
|
||||
function presentFilesMessages() {
|
||||
return [
|
||||
{
|
||||
type: "human",
|
||||
id: "msg-human-present-file",
|
||||
content: [{ type: "text", text: "Create a markdown report" }],
|
||||
},
|
||||
{
|
||||
type: "ai",
|
||||
id: "msg-ai-present-file",
|
||||
content: "The report has been written. Now let me present the file.",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "present-file-artifact",
|
||||
name: "present_files",
|
||||
args: {
|
||||
filepaths: [PRESENTED_ARTIFACT_PATH],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
test.describe("Artifact preview stability", () => {
|
||||
test("renders preview iframe for an in-progress write artifact", async ({
|
||||
page,
|
||||
|
|
@ -235,4 +261,45 @@ test.describe("Artifact preview stability", () => {
|
|||
artifactsPanel.getByText('"中文字段": "测试内容"'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows the title for a presented artifact missing from thread artifacts", async ({
|
||||
page,
|
||||
}) => {
|
||||
mockLangGraphAPI(page, {
|
||||
threads: [
|
||||
{
|
||||
thread_id: PRESENTED_THREAD_ID,
|
||||
title: "Presented artifact title fallback",
|
||||
messages: presentFilesMessages(),
|
||||
artifacts: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await page.route(
|
||||
`**/api/threads/${PRESENTED_THREAD_ID}/artifacts/mnt/user-data/outputs/presented-report.md`,
|
||||
(route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "text/markdown",
|
||||
body: "# Presented Report\n\nGenerated content",
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto(`/workspace/chats/${PRESENTED_THREAD_ID}`);
|
||||
|
||||
// The file card in the message list shows the basename only.
|
||||
await expect(page.getByText("presented-report.md")).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await page.getByText("presented-report.md").first().click();
|
||||
|
||||
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();
|
||||
|
||||
// 2. Preview content should still render correctly.
|
||||
await expect(artifactsPanel.getByText("Presented Report")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue