Skip to content

Commit b833e88

Browse files
committed
Subagent rendering
1 parent d7ab16f commit b833e88

File tree

3 files changed

+455
-140
lines changed

3 files changed

+455
-140
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ interface ThinkingBlockProps {
2929
isStreaming?: boolean
3030
/** Whether there are more content blocks after this one (e.g., tool calls) */
3131
hasFollowingContent?: boolean
32+
/** Custom label for the thinking block (e.g., "Thinking", "Exploring"). Defaults to "Thought" */
33+
label?: string
34+
/** Whether special tags (plan, options) are present - triggers collapse */
35+
hasSpecialTags?: boolean
3236
}
3337

3438
/**
@@ -44,6 +48,8 @@ export function ThinkingBlock({
4448
content,
4549
isStreaming = false,
4650
hasFollowingContent = false,
51+
label = 'Thought',
52+
hasSpecialTags = false,
4753
}: ThinkingBlockProps) {
4854
const [isExpanded, setIsExpanded] = useState(false)
4955
const [duration, setDuration] = useState(0)
@@ -115,30 +121,34 @@ export function ThinkingBlock({
115121
}
116122

117123
const hasContent = content && content.trim().length > 0
118-
// Thinking is "done" when streaming ends OR when there's following content (like a tool call)
119-
const isThinkingDone = !isStreaming || hasFollowingContent
120-
const label = isThinkingDone ? 'Thought' : 'Thinking'
121-
const durationText = ` for ${formatDuration(duration)}`
122-
123-
return (
124-
<div className='mt-1 mb-0'>
125-
<button
126-
onClick={() => {
127-
setIsExpanded((v) => {
128-
const next = !v
129-
// If user collapses during streaming, remember to not auto-expand again
130-
if (!next && isStreaming) userCollapsedRef.current = true
131-
return next
132-
})
133-
}}
134-
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
135-
type='button'
136-
disabled={!hasContent}
137-
>
138-
<span className='relative inline-block'>
139-
<span className='text-[var(--text-tertiary)]'>{label}</span>
140-
<span className='text-[var(--text-muted)]'>{durationText}</span>
141-
{!isThinkingDone && (
124+
// Thinking is "done" when streaming ends OR when there's following content (like a tool call) OR when special tags appear
125+
const isThinkingDone = !isStreaming || hasFollowingContent || hasSpecialTags
126+
const durationText = `${label} for ${formatDuration(duration)}`
127+
// Convert past tense label to present tense for streaming (e.g., "Thought" → "Thinking")
128+
const getStreamingLabel = (lbl: string) => {
129+
if (lbl === 'Thought') return 'Thinking'
130+
if (lbl.endsWith('ed')) return `${lbl.slice(0, -2)}ing`
131+
return lbl
132+
}
133+
const streamingLabel = getStreamingLabel(label)
134+
135+
// During streaming: show header with shimmer effect + expanded content
136+
if (!isThinkingDone) {
137+
return (
138+
<div className='mt-1 mb-0'>
139+
<button
140+
onClick={() => {
141+
setIsExpanded((v) => {
142+
const next = !v
143+
if (!next) userCollapsedRef.current = true
144+
return next
145+
})
146+
}}
147+
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
148+
type='button'
149+
>
150+
<span className='relative inline-block'>
151+
<span className='text-[var(--text-tertiary)]'>{streamingLabel}</span>
142152
<span
143153
aria-hidden='true'
144154
className='pointer-events-none absolute inset-0 select-none overflow-hidden'
@@ -156,19 +166,49 @@ export function ThinkingBlock({
156166
mixBlendMode: 'screen',
157167
}}
158168
>
159-
{label}
160-
{durationText}
169+
{streamingLabel}
161170
</span>
162171
</span>
172+
</span>
173+
{hasContent && (
174+
<ChevronUp
175+
className={clsx(
176+
'h-3 w-3 transition-transform',
177+
isExpanded ? 'rotate-180' : 'rotate-90'
178+
)}
179+
aria-hidden='true'
180+
/>
181+
)}
182+
</button>
183+
184+
<div
185+
ref={scrollContainerRef}
186+
className={clsx(
187+
'overflow-y-auto transition-all duration-300 ease-in-out',
188+
isExpanded ? 'mt-1 max-h-[200px] opacity-100' : 'max-h-0 opacity-0'
163189
)}
164-
<style>{`
165-
@keyframes thinking-shimmer {
166-
0% { background-position: 150% 0; }
167-
50% { background-position: 0% 0; }
168-
100% { background-position: -150% 0; }
169-
}
170-
`}</style>
171-
</span>
190+
>
191+
<pre className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-tertiary)] leading-[1.15rem]'>
192+
{content}
193+
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-tertiary)]' />
194+
</pre>
195+
</div>
196+
</div>
197+
)
198+
}
199+
200+
// After done: show collapsible header with duration
201+
return (
202+
<div className='mt-1 mb-0'>
203+
<button
204+
onClick={() => {
205+
setIsExpanded((v) => !v)
206+
}}
207+
className='mb-1 inline-flex items-center gap-1 text-left font-[470] font-season text-[var(--text-secondary)] text-sm transition-colors hover:text-[var(--text-primary)]'
208+
type='button'
209+
disabled={!hasContent}
210+
>
211+
<span className='text-[var(--text-tertiary)]'>{durationText}</span>
172212
{hasContent && (
173213
<ChevronUp
174214
className={clsx(
@@ -180,20 +220,17 @@ export function ThinkingBlock({
180220
)}
181221
</button>
182222

183-
{isExpanded && (
184-
<div
185-
ref={scrollContainerRef}
186-
className='ml-1 overflow-y-auto border-[var(--border-1)] border-l-2 pl-2'
187-
style={{ maxHeight: THINKING_MAX_HEIGHT }}
188-
>
189-
<pre className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-tertiary)] leading-[1.15rem]'>
190-
{content}
191-
{!isThinkingDone && (
192-
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-tertiary)]' />
193-
)}
194-
</pre>
195-
</div>
196-
)}
223+
<div
224+
ref={scrollContainerRef}
225+
className={clsx(
226+
'overflow-y-auto transition-all duration-300 ease-in-out',
227+
isExpanded ? 'mt-1 max-h-[200px] opacity-100' : 'max-h-0 opacity-0'
228+
)}
229+
>
230+
<pre className='whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-tertiary)] leading-[1.15rem]'>
231+
{content}
232+
</pre>
233+
</div>
197234
</div>
198235
)
199236
}

0 commit comments

Comments
 (0)