feat(client): render user skill invocations as dedicated transcript cards
A user/message carrying the skill-invocation source materializes as its own conversation node (name/args lifted off the source metadata, never re-parsed from the body) and renders as a right-aligned bubble: the /name chip plus the user's trailing text, with the injected <skill_content> collapsed behind a disclosure. A record with an unreadable name degrades to the injected-context row.
This commit is contained in:
@@ -864,6 +864,42 @@ describe('MessageItem arms', () => {
|
||||
view.rerender(<MessageItem t={t} node={node} retryActive />)
|
||||
expect(view.getByRole('status').textContent).toBe('正在重试模型请求(1/2) · 1s')
|
||||
})
|
||||
|
||||
it('skill-invocation renders the /name chip, args, and a collapsed injected body', () => {
|
||||
const body = '<skill_content name="hidden-demo">instructions</skill_content>\n\ncheck the fixture'
|
||||
const view = render(
|
||||
<MessageItem t={t} node={{
|
||||
kind: 'skill-invocation', seq: 4, time: 1_000,
|
||||
name: 'hidden-demo', args: 'check the fixture',
|
||||
content: [{ type: 'text', text: body }] as never,
|
||||
source: null,
|
||||
}}
|
||||
/>,
|
||||
)
|
||||
const chip = view.container.querySelector('[data-ref-chip="skill"]')
|
||||
expect(chip?.textContent).toBe('/hidden-demo')
|
||||
const details = view.container.querySelector('details')
|
||||
expect(details).toBeTruthy()
|
||||
expect(details?.open).toBe(false)
|
||||
expect(view.getByText('查看注入的 skill 内容')).toBeTruthy()
|
||||
expect(view.container.querySelector('pre')?.textContent).toBe(body)
|
||||
expect(view.container.querySelector('[data-skill-invocation]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('skill-invocation without args renders only the chip line', () => {
|
||||
const view = render(
|
||||
<MessageItem t={t} node={{
|
||||
kind: 'skill-invocation', seq: 5, time: 1_000,
|
||||
name: 'bare-skill',
|
||||
content: [{ type: 'text', text: '<skill_content name="bare-skill">x</skill_content>' }] as never,
|
||||
source: null,
|
||||
}}
|
||||
/>,
|
||||
)
|
||||
const bubble = view.container.querySelector('[data-skill-invocation]')
|
||||
expect(bubble?.textContent).toContain('/bare-skill')
|
||||
expect(bubble?.textContent).not.toContain('undefined')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatMessageClock', () => {
|
||||
|
||||
Reference in New Issue
Block a user