fix(chat): place agent activity before response
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

This commit is contained in:
2026-11-05 12:27:22 +08:00
parent 7ee844cd88
commit 43eda89d47
7 changed files with 84 additions and 42 deletions
+39 -9
View File
@@ -100,7 +100,8 @@ test('does not render empty assistant bubbles between tool calls', async ({ page
socket.__trigger('tool.completed', { ...common, event: 'tool.completed', tool_call_id: `tool-${i}`, output: 'done' })
}
}, sessionId)
await expect(page.locator('.message.assistant')).toHaveCount(0)
await expect(page.locator('.message.assistant')).toHaveCount(1)
await expect(page.locator('.message.assistant .message-bubble')).toHaveCount(0)
await page.evaluate((sid) => {
const socket = (window as any).__PW_CHAT_SOCKET__.latest
@@ -138,7 +139,7 @@ test('groups assistant output across tool calls until the run ends', async ({ pa
await expect(page.locator('.run-end')).toHaveText('Run output ended')
})
test('animates the application logo while thinking and respects reduced motion', async ({ page }) => {
test('shows agent activity under its own avatar before the answer', async ({ page }) => {
await authenticate(page, TEST_ACCESS_KEY, 'research')
await mockHermesApi(page)
await mockChatSocket(page)
@@ -146,15 +147,44 @@ test('animates the application logo while thinking and respects reduced motion',
await sendChatMessage(page, 'think')
const { sessionId } = await waitForRunSessionId(page)
await page.evaluate((sid) => {
(window as any).__PW_CHAT_SOCKET__.latest.__trigger('run.started', { event: 'run.started', session_id: sid, run_id: 'thinking-logo' })
const socket = (window as any).__PW_CHAT_SOCKET__.latest
const common = { session_id: sid, run_id: 'activity-layout' }
socket.__trigger('run.started', { ...common, event: 'run.started' })
socket.__trigger('tool.started', { ...common, event: 'tool.started', tool_call_id: 't1', tool: 'terminal' })
}, sessionId)
const avatar = page.locator('.thinking-avatar')
await expect(avatar).toBeVisible()
await expect(avatar).toHaveAttribute('src', '/app-logo.png')
await expect(avatar).toHaveCSS('animation-name', /^thinking-avatar-breathe/)
const assistant = page.locator('.message.assistant')
await expect(assistant).toHaveCount(1)
await expect(assistant.locator('.msg-profile-name')).toHaveText('research')
await expect(assistant.locator('.thinking-status-label')).toBeVisible()
await expect(assistant.locator('.live-tool-group > summary')).toHaveText('Tool calls (1)')
await expect(assistant.locator('.message-bubble')).toHaveCount(0)
await expect(page.locator('.thinking-avatar')).toHaveCount(0)
await page.emulateMedia({ reducedMotion: 'reduce' })
await expect(avatar).toHaveCSS('animation-name', 'none')
await expect(page.locator('.thinking-status-label')).toHaveCSS('animation-name', 'none')
await expect(assistant.locator('.thinking-status-label')).toHaveCSS('animation-name', 'none')
await page.evaluate((sid) => {
const socket = (window as any).__PW_CHAT_SOCKET__.latest
const common = { session_id: sid, run_id: 'activity-layout' }
socket.__trigger('tool.completed', { ...common, event: 'tool.completed', tool_call_id: 't1', output: 'done' })
socket.__trigger('message.delta', { ...common, event: 'message.delta', delta: 'Final answer' })
}, sessionId)
await expect(assistant).toHaveCount(1)
await expect(assistant.locator('.message-bubble')).toContainText('Final answer')
const order = await assistant.evaluate(el => {
const header = el.querySelector('.msg-agent-header')!
const status = el.querySelector('.thinking-status')!
const tool = el.querySelector('.live-tool-group')!
const body = el.querySelector('.message-bubble')!
return !!(header.compareDocumentPosition(status) & Node.DOCUMENT_POSITION_FOLLOWING)
&& !!(status.compareDocumentPosition(tool) & Node.DOCUMENT_POSITION_FOLLOWING)
&& !!(tool.compareDocumentPosition(body) & Node.DOCUMENT_POSITION_FOLLOWING)
})
expect(order).toBe(true)
await page.evaluate((sid) => {
(window as any).__PW_CHAT_SOCKET__.latest.__trigger('run.completed', { event: 'run.completed', session_id: sid, run_id: 'activity-layout' })
}, sessionId)
await expect(assistant.locator('.thinking-status')).toHaveCount(0)
await expect(assistant.locator('.tool-group > summary')).toHaveText('Tool calls (1)')
await expect(assistant.locator('.message-bubble')).toContainText('Final answer')
})
test('opens the session model dialog and the session search from the chat header', async ({ page }) => {
+1
View File
@@ -22,6 +22,7 @@ test('thinking tool rows fill the chat width and shrink without overflowing', as
})
})
const panel = page.locator('.tool-calls-panel')
await panel.locator('.live-tool-group > summary').click()
const row = panel.locator('.tool-call-item').first()
await expect(row).toBeVisible()
for (const width of [1920, 900, 390]) {