Skip to content

Commit 0262662

Browse files
authored
improvement(chat): refine tool activity summaries and icons (#7754)
* improvement(chat): refine tool activity summaries and icons * chore(chat): enforce activity icon catalog coverage
1 parent 7930bfa commit 0262662

11 files changed

Lines changed: 766 additions & 156 deletions

File tree

‎apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function HeroToolCallItem({
2323
<ActivityStatus
2424
label={getToolStatusDisplayTitle(displayTitle, status, toolName)}
2525
isActive={status === 'executing'}
26-
icon={Icon && <Icon className='size-[14px] shrink-0 text-[var(--text-icon)]' />}
26+
icon={<Icon className='size-full' />}
2727
/>
2828
)
2929
return renderStatus ? renderStatus(activity) : activity

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function AgentGroupView({
155155
agentName === 'browser' ? (
156156
<BrowserAgentIcon items={items} />
157157
) : (
158-
<AgentIcon className='size-[16px] text-[var(--text-icon)]' />
158+
<AgentIcon className='size-full' />
159159
)
160160
const isMainAgent = agentName === 'mothership'
161161
/** Open lanes surface their latest work, including work delegated to nested agents. */
@@ -256,15 +256,7 @@ export function AgentGroupView({
256256
) : (
257257
<div className='flex min-w-0 flex-col gap-1.5 py-0.5 pl-6'>{items.map(renderItem)}</div>
258258
)
259-
const header = (
260-
<ActivityStatus
261-
label={headerText}
262-
isActive={isWorking}
263-
icon={
264-
<span className='flex size-[16px] shrink-0 items-center justify-center'>{agentIcon}</span>
265-
}
266-
/>
267-
)
259+
const header = <ActivityStatus label={headerText} isActive={isWorking} icon={agentIcon} />
268260

269261
return (
270262
<div className='flex min-w-0 flex-col gap-1.5'>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,22 @@ function BrowserAgentFavicon({ url, canLoad }: BrowserAgentFaviconProps) {
119119
const [status, setStatus] = useState<'loading' | 'loaded' | 'failed'>('loading')
120120

121121
return (
122-
<span className='relative flex size-[16px] items-center justify-center' aria-hidden='true'>
122+
<span className='relative flex size-full items-center justify-center' aria-hidden='true'>
123123
{/** History alone must not contact a site; keep an already loaded image after the tab closes. */}
124124
{url && status !== 'failed' && (canLoad || status === 'loaded') && (
125125
<img
126126
src={url}
127127
referrerPolicy='no-referrer'
128128
alt=''
129129
className={cn(
130-
'size-[16px] rounded-[3px]',
130+
'size-full rounded-[3px]',
131131
status !== 'loaded' && 'pointer-events-none absolute opacity-0'
132132
)}
133133
onLoad={() => setStatus('loaded')}
134134
onError={() => setStatus('failed')}
135135
/>
136136
)}
137-
{(!url || status !== 'loaded') && <Globe className='size-[16px] text-[var(--text-icon)]' />}
137+
{(!url || status !== 'loaded') && <Globe className='size-full' />}
138138
</span>
139139
)
140140
}

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('getToolActivitySummary', () => {
3535
tool('terminal_run', 'cancelled'),
3636
tool('browser_type', 'rejected'),
3737
])
38-
).toBe('Read files · 1 failed · 1 stopped · 1 skipped')
38+
).toBe('Read files · 2 failed · 1 stopped')
3939
})
4040

4141
it('does not invent actions when all calls failed or were stopped', () => {
@@ -54,6 +54,7 @@ describe('getToolActivitySummary', () => {
5454
})
5555

5656
it.each([
57+
['rejected', 'Failed running checks'],
5758
['skipped', 'Skipped running checks'],
5859
['interrupted', 'Stopped running checks'],
5960
] as const)('labels a single %s tool as finished', (status, expected) => {
@@ -96,6 +97,25 @@ describe('getToolActivitySummary', () => {
9697
).toBe('Read files, searched files +2 more · 1 failed · 1 stopped · 1 skipped')
9798
})
9899

100+
it('uses the same outcome wording for rejected individual and grouped calls', () => {
101+
const rejected = { ...tool('terminal', 'rejected'), displayTitle: 'Running checks' }
102+
expect(getToolActivitySummary([rejected])).toBe('Failed running checks')
103+
expect(getToolActivitySummary([rejected, tool('read', 'skipped')])).toBe(
104+
'Tool activity · 1 failed · 1 skipped'
105+
)
106+
})
107+
108+
it('deduplicates related tools and preserves opposite operations in the summary', () => {
109+
expect(
110+
getToolActivitySummary([
111+
{ ...tool('deploy_as_api'), params: { action: 'deploy' } },
112+
{ ...tool('deploy_as_chat'), params: { action: 'deploy' } },
113+
{ ...tool('deploy_as_mcp'), params: { action: 'undeploy' } },
114+
tool('read'),
115+
])
116+
).toBe('Deployed workflows, undeployed workflows +1 more')
117+
})
118+
99119
it('describes terminal runs from their operation', () => {
100120
expect(
101121
getToolActivitySummary([{ ...tool('terminal'), params: { operation: 'run' } }, tool('read')])

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

Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { type ComponentType, Fragment, useState } from 'react'
44
import { ActivityStatus } from '@/components/ui/activity-status'
5+
import { getToolActivityLabel } from '@/lib/copilot/tools/tool-activity'
56
import { getToolStatusDisplayTitle } from '@/lib/copilot/tools/tool-display'
67
import { ActivityDisclosure } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/activity-disclosure'
78
import type { ToolCallItemProps } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item'
@@ -10,54 +11,6 @@ import { type ToolCallData, ToolCallStatus } from '@/app/workspace/[workspaceId]
1011

1112
const MAX_SUMMARY_ACTIONS = 2
1213

13-
const ACTIVITY_LABELS: Readonly<Record<string, string>> = {
14-
read: 'read files',
15-
read_document: 'read documents',
16-
glob: 'found files',
17-
grep: 'searched files',
18-
web_search: 'searched the web',
19-
web_fetch: 'read web pages',
20-
web_scrape: 'read web pages',
21-
search_library_docs: 'read documentation',
22-
search_knowledge_base: 'searched sources',
23-
call_integration_tool: 'used integrations',
24-
prepare_file_edit: 'prepared file edits',
25-
apply_file_edit: 'edited files',
26-
create_workflow: 'created workflows',
27-
edit_workflow: 'edited workflows',
28-
run_workflow: 'ran workflows',
29-
run_workflow_until_block: 'ran workflows',
30-
deploy_as_api: 'deployed workflows',
31-
table_rows: 'used tables',
32-
terminal: 'used the terminal',
33-
terminal_run: 'ran commands',
34-
terminal_input: 'sent terminal input',
35-
terminal_read: 'read terminal output',
36-
run_function: 'ran code',
37-
run_code: 'ran code',
38-
browser_navigate: 'navigated pages',
39-
browser_open_url: 'navigated pages',
40-
browser_open_tab: 'opened tabs',
41-
browser_switch_tab: 'switched tabs',
42-
browser_close_tab: 'closed tabs',
43-
browser_snapshot: 'read pages',
44-
browser_read_text: 'read pages',
45-
browser_extract: 'read pages',
46-
browser_find: 'searched pages',
47-
browser_click: 'clicked elements',
48-
browser_click_at: 'clicked elements',
49-
browser_drag: 'dragged elements',
50-
browser_type: 'entered text',
51-
browser_insert_text: 'entered text',
52-
browser_fill_form: 'filled forms',
53-
browser_screenshot: 'captured screenshots',
54-
browser_scroll: 'scrolled pages',
55-
browser_select_option: 'selected options',
56-
browser_set_checked: 'updated selections',
57-
open_resource: 'opened resources',
58-
wait: 'waited',
59-
} as const
60-
6114
/** Summarize completed actions without describing failed or skipped work as successful. */
6215
export function getToolActivitySummary(tools: ToolCallData[]): string {
6316
if (tools.length === 1) {
@@ -70,17 +23,12 @@ export function getToolActivitySummary(tools: ToolCallData[]): string {
7023
let skipped = 0
7124
for (const tool of tools) {
7225
if (tool.status === ToolCallStatus.success) {
73-
const label =
74-
tool.toolName === 'terminal' && tool.params?.operation === 'run'
75-
? 'ran commands'
76-
: (ACTIVITY_LABELS[tool.toolName] ??
77-
(tool.toolName.startsWith('browser_') ? 'used the browser' : 'used tools'))
78-
labels.add(label)
79-
} else if (tool.status === ToolCallStatus.error) failed++
26+
labels.add(getToolActivityLabel(tool.toolName, tool.params))
27+
} else if (tool.status === ToolCallStatus.error || tool.status === ToolCallStatus.rejected)
28+
failed++
8029
else if (tool.status === ToolCallStatus.cancelled || tool.status === ToolCallStatus.interrupted)
8130
stopped++
82-
else if (tool.status === ToolCallStatus.skipped || tool.status === ToolCallStatus.rejected)
83-
skipped++
31+
else if (tool.status === ToolCallStatus.skipped) skipped++
8432
}
8533
const summary = Array.from(labels).slice(0, MAX_SUMMARY_ACTIONS).join(', ')
8634
const summaryLabel = summary ? summary[0].toUpperCase() + summary.slice(1) : 'Tool activity'
@@ -132,7 +80,7 @@ export function ToolActivityGroup({
13280
<ActivityStatus
13381
label={getToolActivitySummary(tools)}
13482
isActive={false}
135-
icon={<SummaryIcon className='size-[14px] shrink-0 text-[var(--text-icon)]' />}
83+
icon={<SummaryIcon className='size-full' />}
13684
/>
13785
)
13886
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,9 @@ export function ToolCallItem({
246246
isActive={isExecuting}
247247
icon={
248248
BlockIcon ? (
249-
<BrandIcon icon={BlockIcon} className='size-[14px] shrink-0' />
249+
<BrandIcon icon={BlockIcon} className='size-full' />
250250
) : (
251-
<ToolIcon className='size-[14px] shrink-0 text-[var(--text-icon)]' />
251+
<ToolIcon className='size-full' />
252252
)
253253
}
254254
/>

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.test.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
/**
22
* @vitest-environment node
33
*/
4+
import { Blimp, Wrench } from '@sim/emcn'
45
import { describe, expect, it } from 'vitest'
56
import { collectMessageSources } from '@/app/workspace/[workspaceId]/home/components/message-content/message-sources'
6-
import { deriveMessagePhase, resolveToolDisplayState } from './utils'
7+
import {
8+
deriveMessagePhase,
9+
getAgentIcon,
10+
getToolIcon,
11+
resolveToolDisplayState,
12+
} from '@/app/workspace/[workspaceId]/home/components/message-content/utils'
713

814
describe('deriveMessagePhase', () => {
915
it('is streaming whenever the transport is live', () => {
@@ -59,3 +65,13 @@ describe('collectMessageSources', () => {
5965
expect(collectMessageSources(['Plain prose.', ''])).toEqual([])
6066
})
6167
})
68+
69+
describe('unknown activity icons', () => {
70+
it.each(['future_tool', '', 'constructor', 'toString', '__proto__'])(
71+
'uses fallback icons for %s',
72+
(name) => {
73+
expect(getToolIcon(name)).toBe(Wrench)
74+
expect(getAgentIcon(name)).toBe(Blimp)
75+
}
76+
)
77+
})

0 commit comments

Comments
 (0)