Skip to content

Commit 69326d2

Browse files
BillLeoutsakosvl346Bill Leoutsakos
andauthored
improvement(chat): show live search sources by default (#8419)
* improvement(chat): show live search sources by default * test(chat): cover live search disclosure transitions --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
1 parent 158ead8 commit 69326d2

2 files changed

Lines changed: 99 additions & 2 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
7+
import { ActivityStatus } from '@/components/ui/activity-status'
8+
import { ToolActivityGroup } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group'
9+
import type { ToolCallItemProps } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item'
10+
import type { ToolCallData } from '@/app/workspace/[workspaceId]/home/types'
11+
12+
const executingSearch: ToolCallData = {
13+
id: 'search-1',
14+
toolName: 'search_workspace',
15+
displayTitle: 'Searching workspace',
16+
status: 'executing',
17+
}
18+
19+
const completedSearch: ToolCallData = {
20+
...executingSearch,
21+
status: 'success',
22+
result: {
23+
success: true,
24+
output: {
25+
success: true,
26+
data: {
27+
results: [
28+
{
29+
citationId: 'document:guide',
30+
citationUrl: 'https://example.test/guide',
31+
documentName: 'Guide',
32+
},
33+
],
34+
},
35+
},
36+
},
37+
}
38+
39+
function TestToolCall({ displayTitle, renderStatus }: ToolCallItemProps) {
40+
const status = { label: displayTitle, activeLabel: displayTitle, isActive: false, icon: null }
41+
return renderStatus ? renderStatus(status) : <ActivityStatus {...status} />
42+
}
43+
44+
describe('ToolActivityGroup search disclosure', () => {
45+
let container: HTMLDivElement
46+
let root: Root
47+
48+
beforeEach(() => {
49+
container = document.createElement('div')
50+
document.body.append(container)
51+
root = createRoot(container)
52+
})
53+
54+
afterEach(() => {
55+
act(() => root.unmount())
56+
container.remove()
57+
})
58+
59+
function render(tool: ToolCallData, isLive: boolean) {
60+
act(() =>
61+
root.render(
62+
<ToolActivityGroup tools={[tool]} isLive={isLive} ToolCallComponent={TestToolCall} />
63+
)
64+
)
65+
}
66+
67+
function disclosure() {
68+
const button = container.querySelector<HTMLElement>('[role="button"][aria-expanded]')
69+
if (!button) throw new Error('Expected a search activity disclosure')
70+
return button
71+
}
72+
73+
it('opens when live results arrive, closes for the answer, and respects manual choices', () => {
74+
render(executingSearch, true)
75+
expect(container.querySelector('[role="button"][aria-expanded]')).toBeNull()
76+
77+
render(completedSearch, true)
78+
expect(disclosure().getAttribute('aria-expanded')).toBe('true')
79+
expect(container.querySelector('a[href="https://example.test/guide"]')).not.toBeNull()
80+
81+
render(completedSearch, false)
82+
expect(disclosure().getAttribute('aria-expanded')).toBe('false')
83+
84+
act(() => disclosure().click())
85+
expect(disclosure().getAttribute('aria-expanded')).toBe('true')
86+
render(completedSearch, false)
87+
expect(disclosure().getAttribute('aria-expanded')).toBe('true')
88+
render(completedSearch, true)
89+
expect(disclosure().getAttribute('aria-expanded')).toBe('true')
90+
91+
act(() => disclosure().click())
92+
render(completedSearch, false)
93+
render(completedSearch, true)
94+
expect(disclosure().getAttribute('aria-expanded')).toBe('false')
95+
})
96+
})

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export function ToolActivityGroup({
220220
autoScrollActivity = true,
221221
isLive = false,
222222
}: ToolActivityGroupProps) {
223-
const [expanded, setExpanded] = useState(false)
223+
const [manualExpanded, setManualExpanded] = useState<boolean | null>(null)
224224
const tools = withoutRetriedSearchFailures(calls)
225225
const statusTool = getActivityStatusTool(tools)
226226
if (!statusTool) return null
@@ -236,6 +236,7 @@ export function ToolActivityGroup({
236236
const attentionKey = getActivityAttentionKey(tools)
237237
const entries = tools.map((tool) => ({ tool, sources: getSearchActivitySources(tool) }))
238238
const hasSearchDetails = entries.some(({ sources }) => sources !== undefined)
239+
const expanded = manualExpanded ?? (isLive && hasSearchDetails)
239240

240241
return (
241242
<ToolCallComponent
@@ -262,7 +263,7 @@ export function ToolActivityGroup({
262263
expandedLabel={tools.length > 1 ? groupedActivity?.title : undefined}
263264
collapsible={tools.length > 1 || hasSearchDetails}
264265
expanded={expanded}
265-
onToggle={() => setExpanded(!expanded)}
266+
onToggle={() => setManualExpanded(!expanded)}
266267
isStreaming={working && autoScrollActivity}
267268
unbounded={entries.some(({ sources }) => (sources?.length ?? 0) > 0)}
268269
>

0 commit comments

Comments
 (0)