fix(chat): group run output and hide empty messages
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { groupRunMessages } from '../../packages/client/src/components/hermes/chat/groupRunMessages'
|
||||
import type { Message } from '../../packages/client/src/stores/hermes/chat'
|
||||
|
||||
const message = (id: string, role: Message['role'], content = ''): Message => ({ id, role, content, timestamp: 1 })
|
||||
|
||||
describe('groupRunMessages', () => {
|
||||
it('groups text across tool calls and marks completion only when the run ends', () => {
|
||||
const input = [message('u', 'user', 'question'), message('a', 'assistant', 'first'), message('t', 'tool'), message('b', 'assistant', 'second')]
|
||||
const active = groupRunMessages(input, true)
|
||||
expect(active.map(m => m.role)).toEqual(['user', 'assistant', 'tool'])
|
||||
expect(active[1]).toMatchObject({ id: 'b', content: 'first\n\nsecond' })
|
||||
const finished = groupRunMessages(input, false)
|
||||
expect(finished.at(-1)?.systemType).toBe('run-end')
|
||||
})
|
||||
|
||||
it('keeps separate user turns and explicit run markers separate', () => {
|
||||
const input = [message('u', 'user'), { ...message('a', 'assistant', 'one'), runMarker: '1' }, { ...message('b', 'assistant', 'two'), runMarker: '2' }, message('u2', 'user'), message('c', 'assistant', 'three')]
|
||||
expect(groupRunMessages(input, false).filter(m => m.role === 'assistant').map(m => m.content)).toEqual(['one', 'two', 'three'])
|
||||
expect(groupRunMessages(input, false).filter(m => m.systemType === 'run-end')).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
@@ -82,6 +82,62 @@ test('streams a run and renders markdown, code, math and mermaid without leaking
|
||||
expect(api.unexpectedRequests).toEqual([])
|
||||
})
|
||||
|
||||
test('does not render empty assistant bubbles between tool calls', async ({ page }) => {
|
||||
await authenticate(page, TEST_ACCESS_KEY, 'research')
|
||||
await mockHermesApi(page)
|
||||
await mockChatSocket(page)
|
||||
await page.goto('/#/hermes/chat')
|
||||
await sendChatMessage(page, 'run tools')
|
||||
const { sessionId } = await waitForRunSessionId(page)
|
||||
|
||||
await page.evaluate((sid) => {
|
||||
const socket = (window as any).__PW_CHAT_SOCKET__.latest
|
||||
const common = { session_id: sid, run_id: 'blank-bubbles' }
|
||||
socket.__trigger('run.started', { ...common, event: 'run.started' })
|
||||
for (let i = 0; i < 3; i++) {
|
||||
socket.__trigger('message.delta', { ...common, event: 'message.delta', delta: '\n ' })
|
||||
socket.__trigger('tool.started', { ...common, event: 'tool.started', tool_call_id: `tool-${i}`, tool: 'terminal' })
|
||||
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 page.evaluate((sid) => {
|
||||
const socket = (window as any).__PW_CHAT_SOCKET__.latest
|
||||
socket.__trigger('message.delta', { event: 'message.delta', session_id: sid, run_id: 'blank-bubbles', delta: '\n' })
|
||||
socket.__trigger('message.delta', { event: 'message.delta', session_id: sid, run_id: 'blank-bubbles', delta: 'Visible answer' })
|
||||
}, sessionId)
|
||||
await expect(page.locator('.message.assistant')).toHaveCount(1)
|
||||
await expect(page.locator('.message.assistant')).toContainText('Visible answer')
|
||||
})
|
||||
|
||||
test('groups assistant output across tool calls until the run ends', async ({ page }) => {
|
||||
await authenticate(page, TEST_ACCESS_KEY, 'research')
|
||||
await mockHermesApi(page)
|
||||
await mockChatSocket(page)
|
||||
await page.goto('/#/hermes/chat')
|
||||
await sendChatMessage(page, 'inspect')
|
||||
const { sessionId } = await waitForRunSessionId(page)
|
||||
await page.evaluate((sid) => {
|
||||
const socket = (window as any).__PW_CHAT_SOCKET__.latest
|
||||
const common = { session_id: sid, run_id: 'grouped-run' }
|
||||
socket.__trigger('run.started', { ...common, event: 'run.started' })
|
||||
socket.__trigger('message.delta', { ...common, event: 'message.delta', delta: 'First update' })
|
||||
socket.__trigger('tool.started', { ...common, event: 'tool.started', tool_call_id: 't1', tool: 'terminal' })
|
||||
socket.__trigger('tool.completed', { ...common, event: 'tool.completed', tool_call_id: 't1', output: 'done' })
|
||||
socket.__trigger('message.delta', { ...common, event: 'message.delta', delta: 'Second update' })
|
||||
}, sessionId)
|
||||
const assistant = page.locator('.message.assistant')
|
||||
await expect(assistant).toHaveCount(1)
|
||||
await expect(assistant).toContainText('First update')
|
||||
await expect(assistant).toContainText('Second update')
|
||||
await expect(page.locator('.run-end')).toHaveCount(0)
|
||||
await page.evaluate((sid) => {
|
||||
(window as any).__PW_CHAT_SOCKET__.latest.__trigger('run.completed', { event: 'run.completed', session_id: sid, run_id: 'grouped-run' })
|
||||
}, sessionId)
|
||||
await expect(page.locator('.run-end')).toHaveText('Run output ended')
|
||||
})
|
||||
|
||||
test('opens the session model dialog and the session search from the chat header', async ({ page }) => {
|
||||
await authenticate(page, TEST_ACCESS_KEY, 'research')
|
||||
await mockHermesApi(page, { sessions: [sampleSession] })
|
||||
|
||||
Reference in New Issue
Block a user