Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ export function AgentGroup({
status={item.data.status}
/>
) : (
<p
<div
key={`text-${idx}`}
className='whitespace-pre-wrap pl-[24px] font-base text-[13px] text-[var(--text-secondary)]'
className='ml-[24px] rounded-lg border border-[var(--divider)] bg-[var(--surface-4)] px-3 py-2 text-[13px] text-[var(--text-tertiary)] italic'
>
{item.content.trim()}
</p>
</div>
Comment on lines +103 to +108
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing whitespace-pre-wrap causes newlines to collapse

The original <p> element had the Tailwind class whitespace-pre-wrap, which preserves both whitespace sequences and newlines in the rendered output. The replacement <div> drops this class entirely, so any multi-line subagent text (e.g. step-by-step explanations or prose with explicit line breaks) will now be collapsed into a single run of text with no visible line breaks.

Add whitespace-pre-wrap back to the new <div>'s className to restore the previous behavior.

)
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface CredentialTagData {

export type ContentSegment =
| { type: 'text'; content: string }
| { type: 'thinking'; content: string }
| { type: 'options'; data: OptionsTagData }
| { type: 'usage_upgrade'; data: UsageUpgradeTagData }
| { type: 'credential'; data: CredentialTagData }
Expand All @@ -36,7 +37,7 @@ export interface ParsedSpecialContent {
hasPendingTag: boolean
}

const SPECIAL_TAG_NAMES = ['options', 'usage_upgrade', 'credential'] as const
const SPECIAL_TAG_NAMES = ['thinking', 'options', 'usage_upgrade', 'credential'] as const

/**
* Parses inline special tags (`<options>`, `<usage_upgrade>`) from streamed
Expand Down Expand Up @@ -103,11 +104,17 @@ export function parseSpecialTags(content: string, isStreaming: boolean): ParsedS
}

const body = content.slice(bodyStart, closeIdx)
try {
const data = JSON.parse(body)
segments.push({ type: nearestTagName as 'options' | 'usage_upgrade' | 'credential', data })
} catch {
/* malformed JSON — drop the tag silently */
if (nearestTagName === 'thinking') {
if (body.trim()) {
segments.push({ type: 'thinking', content: body })
}
} else {
try {
const data = JSON.parse(body)
segments.push({ type: nearestTagName as 'options' | 'usage_upgrade' | 'credential', data })
} catch {
/* malformed JSON — drop the tag silently */
}
}

cursor = closeIdx + closeTag.length
Expand Down Expand Up @@ -137,6 +144,13 @@ interface SpecialTagsProps {
*/
export function SpecialTags({ segment, onOptionSelect }: SpecialTagsProps) {
switch (segment.type) {
/** TODO: FIX THINKING BLOCK RENDERING*/
case 'thinking':
return (
<div className='rounded-lg border border-[var(--divider)] bg-[var(--surface-4)] px-3 py-2 text-[13px] text-[var(--text-tertiary)] italic'>
{segment.content.trim()}
</div>
)
Comment on lines +147 to +153
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment signals incomplete implementation

The /** TODO: FIX THINKING BLOCK RENDERING*/ comment was introduced alongside the actual rendering code, signalling that the current output is a known placeholder rather than the intended design. Shipping a TODO in production code alongside the feature itself makes it easy to lose track of the intended follow-up work. Consider either completing the rendering before merging, or tracking the remaining work in an issue and removing the comment from the source.

Comment on lines +149 to +153
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing whitespace-pre-wrap on thinking block

The thinking content rendered here follows the same pattern as the agent-group.tsx text div. If the thinking text contains newlines (very common for reasoning traces), they will be collapsed without whitespace-pre-wrap. The same concern applies here as in agent-group.tsx.

Additionally, the identical class string 'rounded-lg border border-[var(--divider)] bg-[var(--surface-4)] px-3 py-2 text-[13px] text-[var(--text-tertiary)] italic' is duplicated verbatim in both files. Consider extracting it into a shared Tailwind class or small presentational component to avoid drift.

Suggested change
return (
<div className='rounded-lg border border-[var(--divider)] bg-[var(--surface-4)] px-3 py-2 text-[13px] text-[var(--text-tertiary)] italic'>
{segment.content.trim()}
</div>
)
<div className='whitespace-pre-wrap rounded-lg border border-[var(--divider)] bg-[var(--surface-4)] px-3 py-2 text-[13px] text-[var(--text-tertiary)] italic'>
{segment.content.trim()}
</div>

case 'options':
return <OptionsDisplay data={segment.data} onSelect={onOptionSelect} />
case 'usage_upgrade':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {

if (block.type === 'text') {
if (!block.content?.trim()) continue
if (block.subagent && group) {
const lastItem = group.items[group.items.length - 1]
if (lastItem?.type === 'text') {
lastItem.content += block.content
} else {
group.items.push({ type: 'text', content: block.content })
}
continue
}
if (group) {
segments.push(group)
group = null
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ export function useChat(

const ensureTextBlock = (): ContentBlock => {
const last = blocks[blocks.length - 1]
if (last?.type === 'text') return last
if (last?.type === 'text' && last.subagent === activeSubagent) return last
const b: ContentBlock = { type: 'text', content: '' }
blocks.push(b)
return b
Expand Down Expand Up @@ -581,6 +581,7 @@ export function useChat(
const tb = ensureTextBlock()
const normalizedChunk = needsBoundaryNewline ? `\n${chunk}` : chunk
tb.content = (tb.content ?? '') + normalizedChunk
if (activeSubagent) tb.subagent = activeSubagent
runningText += normalizedChunk
lastContentSource = contentSource
streamingContentRef.current = runningText
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/workspace/[workspaceId]/home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export type ContentBlockType =
export interface ContentBlock {
type: ContentBlockType
content?: string
subagent?: string
toolCall?: ToolCallInfo
options?: OptionItem[]
}
Expand Down
Loading