fix: unify chat process and reply presentation
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import { mockWorkbenchContext } from "./support/mock-workbench-context";
|
||||
import { responsesFixture } from "./support/responses-fixture";
|
||||
|
||||
for (const width of [1440, 320]) {
|
||||
test(`native user message copy preserves source text at ${width}px`, async ({ page, context }) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await mockWorkbenchContext(page);
|
||||
await mockAgentApi(page);
|
||||
let responseIndex = 0;
|
||||
await page.route("**/api/v1/responses", (route) => route.fulfill({
|
||||
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" },
|
||||
body: responsesFixture([
|
||||
{ type: "start", messageId: `copy-answer-${++responseIndex}` },
|
||||
{ type: "data-final_answer", data: { content: "已核对管道。" } },
|
||||
{ type: "finish", finishReason: "stop" }
|
||||
])
|
||||
}));
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const prompt = "能直接高亮三根管道吗";
|
||||
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill(prompt);
|
||||
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
|
||||
await expect(panel.getByText("已核对管道。", { exact: true })).toBeVisible();
|
||||
const answerParagraph = panel.locator(".agent-streaming-response p").first();
|
||||
await answerParagraph.click({ clickCount: 3 });
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("已核对管道。");
|
||||
const text = panel.locator(".agent-panel-user-message p");
|
||||
await text.click({ clickCount: 3 });
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(prompt);
|
||||
|
||||
// Exact substrings retain native copy behavior.
|
||||
await text.evaluate((paragraph) => {
|
||||
const range = document.createRange();
|
||||
range.setStart(paragraph.firstChild!, 2);
|
||||
range.setEnd(paragraph.firstChild!, 6);
|
||||
window.getSelection()!.removeAllRanges();
|
||||
window.getSelection()!.addRange(range);
|
||||
});
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(prompt.slice(2, 6));
|
||||
|
||||
// A selection containing another message must retain its native separators.
|
||||
const expectedCrossMessage = await panel.evaluate((element) => {
|
||||
const user = element.querySelector(".agent-panel-user-message p")!;
|
||||
const answer = element.querySelector(".agent-streaming-response p")!;
|
||||
const range = document.createRange();
|
||||
range.setStart(user.firstChild!, 0);
|
||||
range.setEnd(answer.firstChild!, answer.textContent!.length);
|
||||
window.getSelection()!.removeAllRanges();
|
||||
window.getSelection()!.addRange(range);
|
||||
return window.getSelection()!.toString();
|
||||
});
|
||||
expect(expectedCrossMessage).toContain("已核对管道。");
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(expectedCrossMessage);
|
||||
|
||||
// Multiline source text keeps blank lines, indentation and interior spaces.
|
||||
const multiline = "第一行\n\n 第二行 内容";
|
||||
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill(multiline);
|
||||
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
|
||||
const lastText = panel.locator(".agent-panel-user-message p").last();
|
||||
await expect(lastText).toHaveText(multiline, { useInnerText: false });
|
||||
await expect(panel.locator(".agent-streaming-response p")).toHaveCount(2);
|
||||
await expect(panel.locator(".agent-streaming-response p").last()).toHaveText("已核对管道。");
|
||||
await panel.evaluate((element) => {
|
||||
const user = element.querySelectorAll(".agent-panel-user-message p");
|
||||
const answer = element.querySelectorAll(".agent-streaming-response p");
|
||||
const range = document.createRange();
|
||||
range.setStart(user[user.length - 1].firstChild!, 0);
|
||||
range.setEnd(answer[answer.length - 1].firstChild!, 0);
|
||||
window.getSelection()!.removeAllRanges();
|
||||
window.getSelection()!.addRange(range);
|
||||
});
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(multiline);
|
||||
});
|
||||
}
|
||||
|
||||
for (const width of [1440, 320]) {
|
||||
test(`assistant rich text keeps meaningful native copy at ${width}px`, async ({ page, context }) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await mockWorkbenchContext(page);
|
||||
await mockAgentApi(page);
|
||||
const markdown = "首段包含**加粗**文字。\n\n第二段保留段落。\n\n```text\n first\n second\n```\n\n| 名称 | 数值 |\n| --- | --- |\n| 流量 | 12 |";
|
||||
await page.route("**/api/v1/responses", (route) => route.fulfill({
|
||||
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" },
|
||||
body: responsesFixture([
|
||||
{ type: "start", messageId: "rich-copy-answer" },
|
||||
{ type: "data-final_answer", data: { content: markdown } },
|
||||
{ type: "finish", finishReason: "stop" }
|
||||
])
|
||||
}));
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill("核对格式");
|
||||
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
|
||||
const response = panel.locator(".agent-streaming-response");
|
||||
await expect(response.locator("table")).toBeVisible();
|
||||
await response.locator("p").first().click({ clickCount: 3 });
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("首段包含加粗文字。");
|
||||
for (const selector of ["p", "pre", "table"]) {
|
||||
const nativeText = await response.evaluate((element, selector) => {
|
||||
const nodes = element.querySelectorAll(selector);
|
||||
const range = document.createRange();
|
||||
if (selector === "p") {
|
||||
range.setStart(nodes[0].firstChild!, 0);
|
||||
range.setEnd(nodes[1].firstChild!, nodes[1].firstChild!.textContent!.length);
|
||||
} else {
|
||||
range.selectNodeContents(nodes[0]);
|
||||
}
|
||||
window.getSelection()!.removeAllRanges();
|
||||
window.getSelection()!.addRange(range);
|
||||
return window.getSelection()!.toString();
|
||||
}, selector);
|
||||
if (selector === "p") expect(nativeText).toContain("\n\n");
|
||||
if (selector === "pre") expect(nativeText).toContain(" first\n second");
|
||||
if (selector === "table") expect(nativeText).toContain("\t");
|
||||
await page.keyboard.press("Control+c");
|
||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(nativeText);
|
||||
}
|
||||
await expect(panel.getByRole("button", { name: /复制/ }).first()).toBeAttached();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user