diff --git a/src/styles.css b/src/styles.css index 1b3ca65..9eb9182 100644 --- a/src/styles.css +++ b/src/styles.css @@ -140,6 +140,7 @@ --acrylic-highlight: rgba(255, 255, 255, 0.68); --shadow-navigation: 0 1px 0 rgba(48, 65, 85, 0.14); --shadow-panel: 0 12px 32px rgba(15, 33, 55, 0.14); + --shadow-agent-history-bottom: 0 12px 12px -12px rgba(15, 33, 55, 0.32); --shadow-control: 0 6px 18px rgba(15, 33, 55, 0.1); --shadow-transient: 0 14px 34px rgba(15, 33, 55, 0.18); --shadow-agent-floating: @@ -244,6 +245,7 @@ --glass-transient: rgba(232, 242, 250, 0.76); --acrylic-outline: rgba(43, 63, 86, 0.28); --shadow-panel: 0 14px 36px rgba(7, 18, 32, 0.22); + --shadow-agent-history-bottom: 0 14px 14px -14px rgba(7, 18, 32, 0.4); --shadow-control: 0 7px 20px rgba(7, 18, 32, 0.16); --shadow-transient: 0 16px 38px rgba(7, 18, 32, 0.26); --shadow-agent-floating: @@ -681,14 +683,18 @@ button:disabled { z-index: 2; border-color: var(--acrylic-outline); border-style: solid; - border-width: 0 1px 1px; + border-width: 0 0 1px; border-bottom-right-radius: var(--radius-panel); border-bottom-left-radius: var(--radius-panel); - box-shadow: var(--shadow-panel); + box-shadow: var(--shadow-agent-history-bottom); +} + +.agent-panel-integrated-header { + inset-inline: -0.25rem; + top: -0.25rem; } .agent-panel-header-mobile { - inset-inline: -0.25rem; top: -2rem; padding-top: 2rem; } diff --git a/tests/browser/agent-acrylic.e2e.ts b/tests/browser/agent-acrylic.e2e.ts index ea1717e..ad16c69 100644 --- a/tests/browser/agent-acrylic.e2e.ts +++ b/tests/browser/agent-acrylic.e2e.ts @@ -117,6 +117,7 @@ test("desktop Agent header expands into one attached acrylic history surface", a const headerBefore = await agentHeader.boundingBox(); const conversationBefore = await agentConversation.boundingBox(); const historyButtonPresentationBefore = await getSurfacePresentation(historyButton); + const agentPanelFrameBefore = await expectAcrylicOutline(agentPanel); await historyButton.click(); @@ -127,6 +128,8 @@ test("desktop Agent header expands into one attached acrylic history surface", a await page.mouse.move(720, 320); await expectAttachedHistoryExtension(history); await expectTransparentExpandedHeader(agentHeader, agentPanel); + await expectUnifiedHistoryStructure(history); + await expect.poll(() => getAcrylicFrame(agentPanel)).toEqual(agentPanelFrameBefore); await expect .poll(() => getSurfacePresentation(historyButton)) .toEqual(historyButtonPresentationBefore); @@ -144,9 +147,11 @@ test("desktop Agent header expands into one attached acrylic history surface", a expectRoundedValue(headerBox!.y, headerBefore!.y); expect(headerBox!.height).toBeGreaterThan(headerBefore!.height); expectRoundedValue(historyBox!.y, headerBefore!.y + headerBefore!.height); - expectRoundedValue(historyBox!.x, headerBox!.x + 1); - expectRoundedValue(historyBox!.width, headerBox!.width - 2); - expect(headerBox!.width).toBeLessThanOrEqual(agentPanelBox!.width); + expectRoundedValue(headerBox!.x, agentPanelBox!.x + 1); + expectRoundedValue(headerBox!.y, agentPanelBox!.y + 1); + expectRoundedValue(headerBox!.width, agentPanelBox!.width - 2); + expectRoundedValue(historyBox!.x, headerBox!.x); + expectRoundedValue(historyBox!.width, headerBox!.width); expectRoundedBox(conversationAfter!, conversationBefore!); await history.getByRole("button", { name: "重命名对话" }).first().click(); @@ -164,6 +169,7 @@ test("desktop Agent header expands into one attached acrylic history surface", a await expect(history).toBeHidden(); await expect(historyButton).toBeFocused(); await expect(historyButton).toHaveAttribute("aria-expanded", "false"); + await expect.poll(() => getAcrylicFrame(agentPanel)).toEqual(agentPanelFrameBefore); await historyButton.click(); await expect(history).toBeVisible(); @@ -193,6 +199,7 @@ test("mobile Agent mirrors the desktop floating history surface", async ({ const headerBefore = await agentHeader.boundingBox(); const conversationBefore = await agentConversation.boundingBox(); const historyButtonPresentationBefore = await getSurfacePresentation(historyButton); + const mobileSheetFrameBefore = await expectAcrylicOutline(mobileSheet); await historyButton.click(); @@ -202,6 +209,9 @@ test("mobile Agent mirrors the desktop floating history surface", async ({ await page.mouse.move(10, 100); await expectAttachedHistoryExtension(history); await expectTransparentExpandedHeader(agentHeader, mobileSheet); + await expectUnifiedHistoryStructure(history); + await expect.poll(() => getAcrylicFrame(mobileSheet)).toEqual(mobileSheetFrameBefore); + await expect(agentPanel).toHaveCSS("box-shadow", "none"); await expect .poll(() => getSurfacePresentation(historyButton)) .toEqual(historyButtonPresentationBefore); @@ -223,7 +233,7 @@ test("mobile Agent mirrors the desktop floating history surface", async ({ expectRoundedValue(headerAfter!.width, panelBox!.width); expect(headerAfter!.height).toBeGreaterThan(headerBefore!.height); expectRoundedValue(historyBox!.y, headerBefore!.y + headerBefore!.height); - expectRoundedValue(historyBox!.width, headerAfter!.width - 2); + expectRoundedValue(historyBox!.width, headerAfter!.width); expect(headerAfter!.height).toBeLessThan(panelBox!.height); expectRoundedBox(conversationAfter!, conversationBefore!); await expect(agentConversation).toHaveClass(/agent-panel-conversation-canvas/); @@ -344,6 +354,57 @@ async function expectAttachedHistoryExtension(history: Locator) { expect(presentation.boxShadow).toBe("none"); } +async function getAcrylicFrame(surface: Locator) { + return surface.evaluate((element) => { + const style = getComputedStyle(element); + + return { + borderBottomColor: style.borderBottomColor, + borderLeftColor: style.borderLeftColor, + borderRightColor: style.borderRightColor, + borderTopColor: style.borderTopColor, + boxShadow: style.boxShadow + }; + }); +} + +async function expectAcrylicOutline(surface: Locator) { + const frame = await getAcrylicFrame(surface); + + expect(frame.borderTopColor).not.toBe("rgba(0, 0, 0, 0)"); + expect(frame.borderRightColor).not.toBe("rgba(0, 0, 0, 0)"); + expect(frame.borderBottomColor).not.toBe("rgba(0, 0, 0, 0)"); + expect(frame.borderLeftColor).not.toBe("rgba(0, 0, 0, 0)"); + expect(frame.boxShadow).toContain("0px 1px 0px 0px inset"); + expect(frame.boxShadow).toContain("0px 0px 0px 1px"); + + return frame; +} + +async function expectUnifiedHistoryStructure(history: Locator) { + const structure = await history.evaluate((element) => { + const paragraphs = Array.from(element.querySelectorAll("p")); + const heading = paragraphs.find((paragraph) => paragraph.textContent?.trim() === "历史记录"); + const count = paragraphs.find((paragraph) => + /^(?:\d+ 个会话|暂无历史会话)$/.test(paragraph.textContent?.trim() ?? "") + ); + + return { + countTagName: count?.tagName ?? null, + headingTagName: heading?.tagName ?? null, + siblings: Boolean(heading && count && heading.parentElement === count.parentElement) + }; + }); + + expect(structure).toEqual({ + countTagName: "P", + headingTagName: "P", + siblings: true + }); + await expect(history.getByRole("button", { name: "重命名对话" }).first()).toBeVisible(); + await expect(history.getByRole("button", { name: "删除对话" }).first()).toBeVisible(); +} + async function expectTransparentExpandedHeader(header: Locator, agentSurface: Locator) { const [presentation, agentOutlineColor] = await Promise.all([ header.evaluate((element) => { @@ -371,10 +432,10 @@ async function expectTransparentExpandedHeader(header: Locator, agentSurface: Lo expect(presentation.borderBottomLeftRadius).toBe("16px"); expect(presentation.borderBottomRightRadius).toBe("16px"); expect(presentation.borderBottomWidth).toBe("1px"); - expect(presentation.borderLeftWidth).toBe("1px"); - expect(presentation.borderRightWidth).toBe("1px"); + expect(presentation.borderLeftWidth).toBe("0px"); + expect(presentation.borderRightWidth).toBe("0px"); expect(presentation.borderTopWidth).toBe("0px"); - expect(presentation.boxShadow).not.toBe("none"); + expect(presentation.boxShadow).toContain("0px 12px 12px -12px"); } async function getSurfacePresentation(surface: Locator) {