feat: render research Markdown and navigate citations to deck posts

This commit is contained in:
2026-09-24 18:18:15 +09:00
parent 9c3917541b
commit bd9c1b77b7
26 changed files with 1970 additions and 20 deletions
+52 -1
View File
@@ -34,6 +34,7 @@ const running: ResearchRun = {
deckVersion: 1,
startedAt: 1000,
message: '投稿を調べています',
citations: [],
messages: [{ id: 'u1', role: 'user', text: 'WebMCPの反応' }],
deck: { id: 'plan', title: 'WebMCP調査', columns: [] },
}
@@ -72,6 +73,7 @@ function show(connections = [connection], ready = true) {
})
clients.push(client)
const onOpenDeck = vi.fn()
const onNavigateCitation = vi.fn().mockReturnValue(true)
const ui = (
ready = true,
contextDeck = { id: 'manual', title: '手動デッキ', columns: [] },
@@ -82,10 +84,11 @@ function show(connections = [connection], ready = true) {
contextDeck={contextDeck}
connections={connections}
onOpenDeck={onOpenDeck}
onNavigateCitation={onNavigateCitation}
/>
</QueryClientProvider>
)
return { ...render(ui(ready)), client, ui, onOpenDeck }
return { ...render(ui(ready)), client, ui, onOpenDeck, onNavigateCitation }
}
it('retains input and uses the same request ID when retrying a failed start', async () => {
@@ -464,3 +467,51 @@ it('does not let a late reset response erase a newer streamed conversation', asy
expect(view.client.getQueryData(['research-status'])).toEqual(newer)
expect(screen.getByText('別端末の新しい話題')).toBeVisible()
})
it('renders assistant Markdown and forwards its citation with the current evidence', async () => {
const citations: ResearchRun['citations'] = [
{
column: {
id: 'source',
title: '投稿元',
connectionId: 'first',
source: { platform: 'twitter', kind: 'user', target: 'author' },
},
post: {
key: 'twitter:123',
platform: 'twitter',
nativeId: '123',
url: 'https://x.com/author/status/123',
text: '調査に使った投稿',
author: { name: 'Author', handle: 'author' },
},
},
]
current = {
configured: true,
run: {
...running,
citations,
messages: [
{ id: 'u1', role: 'user', text: '# ユーザーの入力はそのまま' },
{
id: 'a1',
role: 'assistant',
text: '# 調査結果\n\n[根拠の投稿](https://x.com/author/status/123)',
},
{ id: 't1', role: 'tool', text: '**ツールの活動もそのまま**' },
],
},
}
const view = show()
expect(
await screen.findByRole('heading', { level: 3, name: '調査結果' }),
).toBeVisible()
expect(screen.getByText('# ユーザーの入力はそのまま')).toBeVisible()
expect(screen.getByText('**ツールの活動もそのまま**')).toBeVisible()
fireEvent.click(screen.getByRole('link', { name: '根拠の投稿' }))
expect(view.onNavigateCitation).toHaveBeenCalledWith(
'https://x.com/author/status/123',
citations,
)
})