Skip to content

Commit 8f6ccf7

Browse files
authored
improvement(chat): simplify inline tool activity (#7744)
* improvement(chat): simplify inline tool activity * fix(chat): replace activity across message segments * fix(chat): retain answered browser handoffs
1 parent 3601f58 commit 8f6ccf7

12 files changed

Lines changed: 473 additions & 198 deletions

File tree

apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ export function HeroChatLoop({
217217
agentLabel='Workflow Agent'
218218
items={WORKFLOW_AGENT_BUILDING_ITEMS}
219219
isStreaming
220-
isCurrentSection
221220
isLaneOpen
222221
defaultExpanded
223222
autoScrollActivity={false}
@@ -237,7 +236,6 @@ export function HeroChatLoop({
237236
agentName='mothership'
238237
agentLabel='Sim'
239238
items={SIM_ITEMS}
240-
defaultExpanded
241239
/>
242240
<HeroChatReply
243241
content={replyComplete ? replyMessage : replyWords.slice(0, revealedWords).join('')}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Table } from '@sim/emcn/icons'
22
import { SlackIcon } from '@/components/icons'
3+
import { ActivityStatus } from '@/components/ui/activity-status'
34
import { getToolStatusDisplayTitle } from '@/lib/copilot/tools/tool-display'
45
import type { ToolCallItemProps } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item'
5-
import { ToolCallRow } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-row'
66

77
/** Demo fixtures have known brands, so the landing page never loads the block registry. */
88
export function HeroToolCallItem({
@@ -18,9 +18,9 @@ export function HeroToolCallItem({
1818
? Table
1919
: undefined
2020
return (
21-
<ToolCallRow
22-
title={getToolStatusDisplayTitle(displayTitle, status, toolName)}
23-
isExecuting={status === 'executing'}
21+
<ActivityStatus
22+
label={getToolStatusDisplayTitle(displayTitle, status, toolName)}
23+
isActive={status === 'executing'}
2424
icon={Icon && <Icon className='size-[14px] shrink-0 text-[var(--text-icon)]' />}
2525
/>
2626
)

apps/sim/app/workspace/[workspaceId]/home/components/knowledge-search-results/knowledge-search-results.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useMemo } from 'react'
44
import { Chip, ChipLink } from '@sim/emcn'
55
import { useQueryStates } from 'nuqs'
6+
import { ShimmerText } from '@/components/ui/shimmer-text'
67
import type {
78
WorkspaceKnowledgeSearchResult,
89
WorkspaceSearchFilters,
@@ -188,7 +189,11 @@ export function KnowledgeSearchResults({
188189
)
189190
}
190191
if (isPending || (isFetching && !results)) {
191-
return <p className='px-2 py-2 text-[var(--text-muted)] text-caption'>Searching…</p>
192+
return (
193+
<p role='status' className='px-2 py-2 text-[var(--text-muted)] text-caption'>
194+
<ShimmerText className='[--shimmer-rest:var(--text-muted)]'>Searching…</ShimmerText>
195+
</p>
196+
)
192197
}
193198

194199
const indexingNote =

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

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ShimmerText } from '@/components/ui'
1414
import { isBrowserAgentAvailable } from '@/lib/browser-agent/transport'
1515
import { RETIRED_BROWSER_REQUEST_TAKEOVER_ID } from '@/lib/copilot/tools/retired-tools'
1616
import { renderInlineMarkdown } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/inline-markdown'
17+
import { getVisibleMainAgentItems } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/main-agent-activity'
1718
import type { ToolCallItemProps } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item'
1819
import {
1920
getAgentIcon,
@@ -47,11 +48,9 @@ export interface AgentGroupProps {
4748
items: AgentGroupItem[]
4849
isDelegating?: boolean
4950
isStreaming?: boolean
50-
/** This group is the latest section in its parent sequence (drives collapse). */
51-
isCurrentSection?: boolean
5251
/** The subagent lane is still open (no subagent_end yet) — i.e. actively running. */
5352
isLaneOpen?: boolean
54-
/** Opens the group on first render without changing production's automatic collapse rules. */
53+
/** Opens a subagent group on first render. */
5554
defaultExpanded?: boolean
5655
/** Keeps the activity viewport anchored at the top while new rows stream in. */
5756
autoScrollActivity?: boolean
@@ -151,7 +150,6 @@ export function AgentGroupView({
151150
items,
152151
isDelegating = false,
153152
isStreaming = false,
154-
isCurrentSection = false,
155153
isLaneOpen = false,
156154
defaultExpanded = false,
157155
autoScrollActivity = true,
@@ -192,26 +190,15 @@ export function AgentGroupView({
192190
const isWorking =
193191
!activeBrowserTakeover && ((isDelegating && !resolved) || (isStreaming && isLaneOpen))
194192

195-
// SUBAGENT groups never auto-expand: the collapsed row IS the live view —
196-
// label plus latest running tool title. Expanding is a deliberate user
197-
// action; only a pending permission prompt or a browser hand-back forces
198-
// one open. The MAIN lane ("Sim") is not a delegation card: its narration
199-
// and tool calls are the turn itself, so it keeps the original live-expand
200-
// behavior (open while streaming/current, settles when superseded).
201-
const autoExpanded = isMainAgent && isStreaming && (isCurrentSection || isLaneOpen || !resolved)
202-
const [manualExpanded, setManualExpanded] = useState<boolean | null>(
203-
defaultExpanded ? true : null
204-
)
193+
const [manualExpanded, setManualExpanded] = useState(defaultExpanded)
205194
const [expandedTakeoverId, setExpandedTakeoverId] = useState<string | null>(null)
206195
// An outstanding permission prompt overrides a manual collapse: the turn
207196
// cannot proceed until it is answered, so hiding it would deadlock the chat
208197
// with nothing on screen to explain why.
209198
const expanded =
210199
hasAwaitingApproval(items) ||
211200
nestedBrowserTakeover ||
212-
(activeBrowserTakeover
213-
? expandedTakeoverId === activeBrowserTakeover.id
214-
: (manualExpanded ?? autoExpanded))
201+
(activeBrowserTakeover ? expandedTakeoverId === activeBrowserTakeover.id : manualExpanded)
215202

216203
const toggleExpanded = () => {
217204
if (activeBrowserTakeover) {
@@ -221,9 +208,54 @@ export function AgentGroupView({
221208
setManualExpanded(!expanded)
222209
}
223210

211+
const visibleItems = isMainAgent ? getVisibleMainAgentItems(items) : items
212+
const activity = (
213+
<div className={cn('flex min-w-0 flex-col gap-1.5', !isMainAgent && 'py-0.5 pl-6')}>
214+
{visibleItems.map((item, idx) => {
215+
if (item.type === 'tool') {
216+
return (
217+
<ToolCallComponent
218+
key={item.data.id}
219+
toolCallId={item.data.id}
220+
toolName={item.data.toolName}
221+
displayTitle={item.data.displayTitle}
222+
status={item.data.status}
223+
params={item.data.params}
224+
result={item.data.result}
225+
streamingArgs={item.data.streamingArgs}
226+
startedAt={item.data.startedAt}
227+
/>
228+
)
229+
}
230+
if (item.type === 'agent_group') {
231+
return (
232+
<AgentGroupView
233+
key={item.group.id}
234+
ToolCallComponent={ToolCallComponent}
235+
renderBrowserTakeover={renderBrowserTakeover}
236+
agentName={item.group.agentName}
237+
agentLabel={item.group.agentLabel}
238+
items={item.group.items}
239+
isDelegating={item.group.isDelegating}
240+
isStreaming={isStreaming}
241+
isLaneOpen={item.group.isOpen}
242+
/>
243+
)
244+
}
245+
return (
246+
<NarrationText
247+
key={`text-${idx}`}
248+
content={item.content}
249+
isStreaming={isStreaming && idx === visibleItems.length - 1}
250+
/>
251+
)
252+
})}
253+
</div>
254+
)
255+
224256
return (
225257
<div className='flex flex-col gap-1.5'>
226-
{hasItems ? (
258+
{isMainAgent ? null : hasItems ? (
227259
<button
228260
type='button'
229261
onClick={toggleExpanded}
@@ -256,60 +288,20 @@ export function AgentGroupView({
256288
)}
257289
</div>
258290
)}
259-
{hasItems && (
291+
{isMainAgent ? (
292+
activity
293+
) : hasItems ? (
260294
<Expandable expanded={expanded}>
261295
<ExpandableContent>
262296
<BoundedViewport
263297
isStreaming={isStreaming && autoScrollActivity}
264298
unbounded={nestedBrowserTakeover}
265299
>
266-
<div className='flex flex-col gap-1.5 py-0.5'>
267-
{items.map((item, idx) => {
268-
if (item.type === 'tool') {
269-
return (
270-
<ToolCallComponent
271-
key={item.data.id}
272-
toolCallId={item.data.id}
273-
toolName={item.data.toolName}
274-
displayTitle={item.data.displayTitle}
275-
status={item.data.status}
276-
params={item.data.params}
277-
result={item.data.result}
278-
streamingArgs={item.data.streamingArgs}
279-
startedAt={item.data.startedAt}
280-
/>
281-
)
282-
}
283-
if (item.type === 'agent_group') {
284-
return (
285-
<div key={item.group.id} className='pl-6'>
286-
<AgentGroupView
287-
ToolCallComponent={ToolCallComponent}
288-
renderBrowserTakeover={renderBrowserTakeover}
289-
agentName={item.group.agentName}
290-
agentLabel={item.group.agentLabel}
291-
items={item.group.items}
292-
isDelegating={item.group.isDelegating}
293-
isStreaming={isStreaming}
294-
isCurrentSection={idx === items.length - 1}
295-
isLaneOpen={item.group.isOpen}
296-
/>
297-
</div>
298-
)
299-
}
300-
return (
301-
<NarrationText
302-
key={`text-${idx}`}
303-
content={item.content}
304-
isStreaming={isStreaming && idx === items.length - 1}
305-
/>
306-
)
307-
})}
308-
</div>
300+
{activity}
309301
</BoundedViewport>
310302
</ExpandableContent>
311303
</Expandable>
312-
)}
304+
) : null}
313305
{activeBrowserTakeover && (
314306
<div key={activeBrowserTakeover.id} className='animate-stream-fade-in'>
315307
{renderBrowserTakeover?.(activeBrowserTakeover.reason)}
@@ -334,7 +326,7 @@ function NarrationText({ content, isStreaming }: NarrationTextProps) {
334326
const revealed = useSmoothText(content, isStreaming)
335327

336328
return (
337-
<span className='pl-6 text-[13px] text-[var(--text-muted)] leading-[18px]'>
329+
<span className='text-[13px] text-[var(--text-muted)] leading-[18px]'>
338330
{renderInlineMarkdown(revealed.trim())}
339331
</span>
340332
)

0 commit comments

Comments
 (0)