mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-13 14:50:36 +00:00
* feat(projects): add visible project memory with extraction controls * fix(projects): avoid replaying committed memory extractions * chore(ci): retrigger project memory checks --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { mockLangGraphAPI } from "./utils/mock-api";
|
|
|
|
test.use({ viewport: { width: 1280, height: 960 } });
|
|
|
|
test("project memory: inspect, toggle, promote and delete", async ({
|
|
page,
|
|
context,
|
|
baseURL,
|
|
}) => {
|
|
const project = {
|
|
id: "project-memory-demo",
|
|
name: "Q4 财报分析",
|
|
instructions: "本项目用于 Q4 财报分析,所有输出使用中文。",
|
|
};
|
|
let enabled = true;
|
|
let facts = [
|
|
"报表金额口径一律不含税",
|
|
"数据拉取统一使用 v2 接口",
|
|
"对比基准年固定为 2024 年",
|
|
].map((content, i) => ({
|
|
id: `project:${i}`,
|
|
content,
|
|
source_thread_id: "demo-chat",
|
|
created_at: "2026-11-10T10:00:00Z",
|
|
}));
|
|
mockLangGraphAPI(page, { projects: [project] });
|
|
await page.route(`**/api/projects/${project.id}`, async (route) => {
|
|
if (route.request().method() === "PATCH")
|
|
Object.assign(project, route.request().postDataJSON());
|
|
await route.fulfill({
|
|
json: {
|
|
...project,
|
|
status: "active",
|
|
presentation: {},
|
|
created_at: "",
|
|
updated_at: "",
|
|
},
|
|
});
|
|
});
|
|
await page.route(`**/api/projects/${project.id}/memory**`, async (route) => {
|
|
const path = new URL(route.request().url()).pathname;
|
|
const method = route.request().method();
|
|
if (method === "PATCH")
|
|
enabled = (route.request().postDataJSON() as { enabled: boolean })
|
|
.enabled;
|
|
if (method === "DELETE")
|
|
facts = facts.filter(
|
|
(fact) => fact.id !== decodeURIComponent(path.split("/").at(-1)!),
|
|
);
|
|
if (method === "POST") {
|
|
const id = decodeURIComponent(path.split("/").at(-2)!);
|
|
const fact = facts.find((item) => item.id === id)!;
|
|
project.instructions += `\n- ${fact.content}`;
|
|
facts = facts.filter((item) => item.id !== id);
|
|
await route.fulfill({
|
|
json: {
|
|
instructions: project.instructions,
|
|
memory: { enabled, facts, max_facts: 30 },
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
await route.fulfill({ json: { enabled, facts, max_facts: 30 } });
|
|
});
|
|
await context.addCookies([{ name: "locale", value: "zh-CN", url: baseURL! }]);
|
|
await page.goto(`/workspace/projects/${project.id}`);
|
|
await page.getByRole("tab", { name: "指令", exact: true }).click();
|
|
await expect(page.getByRole("heading", { name: "项目记忆" })).toBeVisible();
|
|
await expect(page.getByText("3 / 30")).toBeVisible();
|
|
await page.screenshot({
|
|
path:
|
|
process.env.PROJECT_MEMORY_SCREENSHOT ??
|
|
"test-results/project-memory.png",
|
|
fullPage: true,
|
|
animations: "disabled",
|
|
});
|
|
await page.getByRole("switch", { name: "自动提取" }).click();
|
|
await expect(
|
|
page.getByRole("switch", { name: "自动提取" }),
|
|
).not.toBeChecked();
|
|
await expect(
|
|
page.getByText("报表金额口径一律不含税", { exact: true }),
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole("button", { name: "转入指令", exact: true })
|
|
.first()
|
|
.click();
|
|
await expect(
|
|
page.getByRole("textbox", { name: "指令", exact: true }),
|
|
).toHaveValue(/报表金额口径一律不含税/);
|
|
await expect(page.getByText("2 / 30")).toBeVisible();
|
|
await page
|
|
.getByRole("button", { name: "删除: 数据拉取统一使用 v2 接口" })
|
|
.click();
|
|
await expect(page.getByText("1 / 30")).toBeVisible();
|
|
await page.reload();
|
|
await page.getByRole("tab", { name: "指令", exact: true }).click();
|
|
await expect(
|
|
page.getByRole("switch", { name: "自动提取" }),
|
|
).not.toBeChecked();
|
|
await expect(page.getByText("1 / 30")).toBeVisible();
|
|
});
|