fix: refine agent progress feedback
This commit is contained in:
@@ -7,6 +7,8 @@ const STREAM_CHUNK_DELAY_MS = 20;
|
||||
|
||||
let streamServer: Server;
|
||||
let streamServerUrl: string;
|
||||
let failProgress: (() => void) | null = null;
|
||||
let releaseStream: (() => void) | null = null;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
streamServer = createServer((_request, response) => {
|
||||
@@ -36,6 +38,30 @@ test.beforeAll(async () => {
|
||||
started_at: Date.now()
|
||||
}
|
||||
});
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "inspect-rainfall",
|
||||
data: {
|
||||
id: "inspect-rainfall",
|
||||
phase: "evidence",
|
||||
status: "completed",
|
||||
title: "正在核对降雨过程与汇水区响应关系",
|
||||
detail: "读取最近十二小时分钟级降雨序列,并检查每个汇水区的响应延迟是否处于合理范围。",
|
||||
started_at: Date.now()
|
||||
}
|
||||
});
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "inspect-level",
|
||||
data: {
|
||||
id: "inspect-level",
|
||||
phase: "evidence",
|
||||
status: "completed",
|
||||
title: "正在核对液位连续性",
|
||||
detail: "排除缺测与异常峰值。",
|
||||
started_at: Date.now()
|
||||
}
|
||||
});
|
||||
send({
|
||||
type: "data-todo_update",
|
||||
id: "stream-stability-plan",
|
||||
@@ -53,24 +79,11 @@ test.beforeAll(async () => {
|
||||
send({ type: "text-start", id: "answer" });
|
||||
|
||||
let index = 0;
|
||||
const timer = setInterval(() => {
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
const sendNextTextChunk = () => {
|
||||
const delta = chunks[index];
|
||||
if (delta !== undefined) {
|
||||
index += 1;
|
||||
if (index === 3) {
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "assess-risk",
|
||||
data: {
|
||||
id: "assess-risk",
|
||||
phase: "analysis",
|
||||
status: "running",
|
||||
title: "正在判断溢流风险",
|
||||
detail: "结合流量突变与液位趋势形成判断。",
|
||||
started_at: Date.now()
|
||||
}
|
||||
});
|
||||
}
|
||||
send({ type: "text-delta", id: "answer", delta });
|
||||
send({
|
||||
type: "data-stream_token",
|
||||
@@ -84,7 +97,10 @@ test.beforeAll(async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(timer);
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "assess-risk",
|
||||
@@ -100,9 +116,50 @@ test.beforeAll(async () => {
|
||||
send({ type: "text-end", id: "answer" });
|
||||
send({ type: "finish", finishReason: "stop" });
|
||||
response.end();
|
||||
}, STREAM_CHUNK_DELAY_MS);
|
||||
};
|
||||
|
||||
response.on("close", () => clearInterval(timer));
|
||||
while (index < 3 && index < chunks.length) {
|
||||
sendNextTextChunk();
|
||||
}
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "assess-risk",
|
||||
data: {
|
||||
id: "assess-risk",
|
||||
phase: "analysis",
|
||||
status: "running",
|
||||
title: "正在判断溢流风险",
|
||||
detail: "结合流量突变与液位趋势形成判断。",
|
||||
started_at: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
failProgress = () => {
|
||||
send({
|
||||
type: "data-progress",
|
||||
id: "inspect-level",
|
||||
data: {
|
||||
id: "inspect-level",
|
||||
phase: "evidence",
|
||||
status: "error",
|
||||
title: "液位连续性核对失败",
|
||||
detail: "监测数据读取超时。",
|
||||
ended_at: Date.now()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
releaseStream = () => {
|
||||
if (!timer) {
|
||||
timer = setInterval(sendNextTextChunk, STREAM_CHUNK_DELAY_MS);
|
||||
}
|
||||
};
|
||||
|
||||
response.on("close", () => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => streamServer.listen(0, "127.0.0.1", resolve));
|
||||
@@ -204,13 +261,55 @@ test("stream completion and the plan card remain positionally stable", async ({
|
||||
});
|
||||
|
||||
await sendButton.click();
|
||||
const liveProgress = page.getByRole("list", { name: "最近执行进度" });
|
||||
await expect(liveProgress.getByText("正在判断溢流风险", { exact: true })).toBeVisible();
|
||||
await expect(liveProgress.locator("li")).toHaveCount(3);
|
||||
await expect(liveProgress.getByText("正在分析运行上下文", { exact: true })).toHaveCount(0);
|
||||
|
||||
const liveProgressRowHeights = await liveProgress.locator("li").evaluateAll((rows) =>
|
||||
rows.map((row) => row.getBoundingClientRect().height)
|
||||
);
|
||||
expect(liveProgressRowHeights.every((height) => Math.abs(height - 48) <= 0.01)).toBe(true);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const state = window as typeof window & { __agentFailureMotionObserved?: boolean };
|
||||
const startedAt = performance.now();
|
||||
state.__agentFailureMotionObserved = false;
|
||||
|
||||
const sample = () => {
|
||||
const badge = document.querySelector<HTMLElement>('[data-progress-status="error"]');
|
||||
if (badge && getComputedStyle(badge).transform !== "none") {
|
||||
state.__agentFailureMotionObserved = true;
|
||||
}
|
||||
if (!state.__agentFailureMotionObserved && performance.now() - startedAt < 1_000) {
|
||||
requestAnimationFrame(sample);
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(sample);
|
||||
});
|
||||
triggerProgressFailure();
|
||||
await expect(liveProgress.getByText("液位连续性核对失败", { exact: true })).toBeVisible();
|
||||
await expect.poll(() =>
|
||||
page.evaluate(() =>
|
||||
(window as typeof window & { __agentFailureMotionObserved?: boolean }).__agentFailureMotionObserved ?? false
|
||||
)
|
||||
).toBe(true);
|
||||
resumeStream();
|
||||
|
||||
await expect(page.getByText("分析完成", { exact: true }).filter({ visible: true }).first()).toBeVisible({
|
||||
timeout: 10_000
|
||||
});
|
||||
const progressToggle = page.getByRole("button", { name: /执行进度/ }).filter({ visible: true }).first();
|
||||
await expect(progressToggle).toContainText("共 2 条");
|
||||
await expect(page.getByText("溢流风险判断完成", { exact: true }).filter({ visible: true }).first()).toBeVisible();
|
||||
await page.waitForTimeout(400);
|
||||
await expect(progressToggle).toContainText("共 4 条");
|
||||
await expect(page.getByRole("list", { name: "最近执行进度" })).toHaveCount(0);
|
||||
await expect(page.getByText("溢流风险判断完成", { exact: true }).filter({ visible: true })).toHaveCount(0);
|
||||
await expect.poll(async () =>
|
||||
page.evaluate(() => {
|
||||
const scroll = document.querySelector<HTMLElement>(".agent-conversation-scroll");
|
||||
return scroll ? Math.abs((scroll.scrollHeight - scroll.clientHeight) - scroll.scrollTop) : Number.POSITIVE_INFINITY;
|
||||
})
|
||||
).toBeLessThanOrEqual(2);
|
||||
|
||||
const samples = await page.evaluate(() =>
|
||||
(window as typeof window & {
|
||||
@@ -259,6 +358,7 @@ test("stream completion and the plan card remain positionally stable", async ({
|
||||
).toBeLessThanOrEqual(2);
|
||||
|
||||
await progressToggle.click();
|
||||
await expect(page.getByRole("list", { name: "全部执行进度" }).locator("li")).toHaveCount(4);
|
||||
await expect(page.getByText("正在分析运行上下文", { exact: true }).filter({ visible: true }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -282,3 +382,23 @@ function splitIntoCodePointChunks(content: string, chunkSize: number) {
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function resumeStream() {
|
||||
if (!releaseStream) {
|
||||
throw new Error("Stream checkpoint was not reached");
|
||||
}
|
||||
|
||||
const resume = releaseStream;
|
||||
releaseStream = null;
|
||||
resume();
|
||||
}
|
||||
|
||||
function triggerProgressFailure() {
|
||||
if (!failProgress) {
|
||||
throw new Error("Progress failure checkpoint was not reached");
|
||||
}
|
||||
|
||||
const fail = failProgress;
|
||||
failProgress = null;
|
||||
fail();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user