Skip to content

Commit 77a89fd

Browse files
committed
cli: ui tweaks
- fix collapsed command preview extra newline - refactor tool-group spacing - more spacing between tools in main agent - aligned tool names in subagents
1 parent fb953d9 commit 77a89fd

File tree

5 files changed

+174
-40
lines changed

5 files changed

+174
-40
lines changed

cli/src/components/agent-branch-item.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ export const AgentBranchItem = ({
233233
showCollapsedPreview ? (
234234
<box
235235
style={{
236-
paddingLeft: 0,
237-
paddingRight: 0,
236+
paddingLeft: 1,
237+
paddingRight: 1,
238238
paddingTop: 0,
239239
paddingBottom: 0,
240240
}}
@@ -252,8 +252,8 @@ export const AgentBranchItem = ({
252252
style={{
253253
flexDirection: 'column',
254254
gap: 0,
255-
paddingLeft: 0,
256-
paddingRight: 0,
255+
paddingLeft: 1,
256+
paddingRight: 1,
257257
paddingTop: 0,
258258
paddingBottom: 0,
259259
}}
@@ -263,7 +263,6 @@ export const AgentBranchItem = ({
263263
style={{
264264
flexDirection: 'column',
265265
gap: 0,
266-
marginBottom: content ? 1 : 0,
267266
}}
268267
>
269268
<text fg={theme.foreground}>Prompt</text>
@@ -286,7 +285,7 @@ export const AgentBranchItem = ({
286285
<box
287286
style={{
288287
alignSelf: 'flex-end',
289-
marginTop: content ? 0 : 1,
288+
marginTop: 0,
290289
paddingRight: 1,
291290
paddingBottom: 0,
292291
marginBottom: 0,

cli/src/components/message-block.tsx

Lines changed: 126 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,6 @@ export const MessageBlock = ({
221221
? `${paddedPreviewPrefix}${line}`
222222
: blankPreviewPrefix,
223223
)
224-
if (!decorated.some((line) => line.trim().length === 0)) {
225-
decorated.push(blankPreviewPrefix)
226-
}
227224
return decorated.join('\n')
228225
}
229226
const rawStreamingPreview = isStreaming
@@ -398,7 +395,7 @@ export const MessageBlock = ({
398395
key={`agent-${idx}`}
399396
style={{ wrapMode: 'word', fg: theme.foreground }}
400397
>
401-
{` ${identifier}`}
398+
{`• ${identifier}`}
402399
</text>
403400
)
404401
}
@@ -421,6 +418,7 @@ export const MessageBlock = ({
421418
streamingPreview=""
422419
finishedPreview=""
423420
onToggle={() => onToggleCollapsed(agentListBlock.id)}
421+
dense
424422
/>
425423
</box>
426424
)
@@ -436,7 +434,8 @@ export const MessageBlock = ({
436434
const nestedBlocks = agentBlock.blocks ?? []
437435
const nodes: React.ReactNode[] = []
438436

439-
nestedBlocks.forEach((nestedBlock, nestedIdx) => {
437+
for (let nestedIdx = 0; nestedIdx < nestedBlocks.length; ) {
438+
const nestedBlock = nestedBlocks[nestedIdx]
440439
switch (nestedBlock.type) {
441440
case 'text': {
442441
const nestedStatus =
@@ -476,6 +475,7 @@ export const MessageBlock = ({
476475
{renderedContent}
477476
</text>,
478477
)
478+
nestedIdx++
479479
break
480480
}
481481

@@ -498,20 +498,77 @@ export const MessageBlock = ({
498498
})}
499499
</box>,
500500
)
501+
nestedIdx++
501502
break
502503
}
503504

504505
case 'tool': {
505-
const isLastBranch = !hasBranchAfter(nestedBlocks, nestedIdx)
506-
nodes.push(
507-
renderToolBranch(
508-
nestedBlock,
506+
const start = nestedIdx
507+
const toolGroup: Extract<ContentBlock, { type: 'tool' }>[] = []
508+
while (
509+
nestedIdx < nestedBlocks.length &&
510+
nestedBlocks[nestedIdx].type === 'tool'
511+
) {
512+
toolGroup.push(nestedBlocks[nestedIdx] as any)
513+
nestedIdx++
514+
}
515+
516+
const groupNodes = toolGroup.map((toolBlock, idxInGroup) => {
517+
const globalIdx = start + idxInGroup
518+
const isLastBranch = !hasBranchAfter(nestedBlocks, globalIdx)
519+
return renderToolBranch(
520+
toolBlock,
509521
indentLevel,
510522
isLastBranch,
511-
`${keyPrefix}-tool-${nestedBlock.toolCallId}`,
523+
`${keyPrefix}-tool-${toolBlock.toolCallId}`,
512524
ancestorBranchStates,
513-
),
514-
)
525+
)
526+
})
527+
528+
const nonNullGroupNodes = groupNodes.filter(
529+
Boolean,
530+
) as React.ReactNode[]
531+
if (nonNullGroupNodes.length > 0) {
532+
const isRenderableBlock = (b: ContentBlock): boolean => {
533+
if (b.type === 'tool') {
534+
return (b as any).toolName !== 'end_turn'
535+
}
536+
switch (b.type) {
537+
case 'text':
538+
case 'html':
539+
case 'agent':
540+
case 'agent-list':
541+
return true
542+
default:
543+
return false
544+
}
545+
}
546+
547+
// Check for any subsequent renderable blocks without allocating a slice
548+
let hasRenderableAfter = false
549+
for (let i = nestedIdx; i < nestedBlocks.length; i++) {
550+
if (isRenderableBlock(nestedBlocks[i] as any)) {
551+
hasRenderableAfter = true
552+
break
553+
}
554+
}
555+
nodes.push(
556+
<box
557+
key={`${keyPrefix}-tool-group-${start}`}
558+
style={{
559+
flexDirection: 'column',
560+
gap: 0,
561+
// Avoid double spacing with the agent header, which already
562+
// adds bottom padding. Only add top margin if this group is
563+
// not the first rendered child.
564+
marginTop: nodes.length === 0 ? 0 : 1,
565+
marginBottom: hasRenderableAfter ? 1 : 0,
566+
}}
567+
>
568+
{nonNullGroupNodes}
569+
</box>,
570+
)
571+
}
515572
break
516573
}
517574

@@ -526,10 +583,11 @@ export const MessageBlock = ({
526583
ancestorBranchStates,
527584
),
528585
)
586+
nestedIdx++
529587
break
530588
}
531589
}
532-
})
590+
}
533591

534592
return nodes
535593
}
@@ -554,7 +612,7 @@ export const MessageBlock = ({
554612
)
555613
}
556614

557-
const renderBlock = (block: ContentBlock, idx: number) => {
615+
const renderSingleBlock = (block: ContentBlock, idx: number) => {
558616
switch (block.type) {
559617
case 'text': {
560618
const isStreamingText = isLoading || !isComplete
@@ -613,14 +671,8 @@ export const MessageBlock = ({
613671
}
614672

615673
case 'tool': {
616-
const isLastBranch = !hasBranchAfter(blocks, idx)
617-
return renderToolBranch(
618-
block,
619-
0,
620-
isLastBranch,
621-
`${messageId}-tool-${block.toolCallId}`,
622-
[],
623-
)
674+
// Handled in renderBlocks grouping logic
675+
return null
624676
}
625677

626678
case 'agent': {
@@ -648,6 +700,57 @@ export const MessageBlock = ({
648700
}
649701
}
650702

703+
const renderBlocks = (sourceBlocks: ContentBlock[]) => {
704+
const nodes: React.ReactNode[] = []
705+
for (let i = 0; i < sourceBlocks.length; ) {
706+
const block = sourceBlocks[i]
707+
if (block.type === 'tool') {
708+
const start = i
709+
const group: Extract<ContentBlock, { type: 'tool' }>[] = []
710+
while (i < sourceBlocks.length && sourceBlocks[i].type === 'tool') {
711+
group.push(sourceBlocks[i] as any)
712+
i++
713+
}
714+
715+
const groupNodes = group.map((toolBlock, idxInGroup) => {
716+
const globalIdx = start + idxInGroup
717+
const isLastBranch = !hasBranchAfter(sourceBlocks, globalIdx)
718+
return renderToolBranch(
719+
toolBlock,
720+
0,
721+
isLastBranch,
722+
`${messageId}-tool-${toolBlock.toolCallId}`,
723+
[],
724+
)
725+
})
726+
727+
const nonNullGroupNodes = groupNodes.filter(
728+
Boolean,
729+
) as React.ReactNode[]
730+
if (nonNullGroupNodes.length > 0) {
731+
nodes.push(
732+
<box
733+
key={`${messageId}-tool-group-${start}`}
734+
style={{
735+
flexDirection: 'column',
736+
gap: 0,
737+
marginTop: 1,
738+
marginBottom: 1,
739+
}}
740+
>
741+
{nonNullGroupNodes}
742+
</box>,
743+
)
744+
}
745+
continue
746+
}
747+
748+
nodes.push(renderSingleBlock(block, i))
749+
i++
750+
}
751+
return nodes
752+
}
753+
651754
return (
652755
<>
653756
{isUser && (
@@ -666,7 +769,7 @@ export const MessageBlock = ({
666769
)}
667770
{blocks ? (
668771
<box style={{ flexDirection: 'column', gap: 0, width: '100%' }}>
669-
{blocks.map(renderBlock)}
772+
{renderBlocks(blocks)}
670773
</box>
671774
) : (
672775
renderSimpleContent()

cli/src/components/tools/read-files.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TextAttributes } from '@opentui/core'
22
import React from 'react'
3+
import stringWidth from 'string-width'
34

45
import { useTheme } from '../../hooks/use-theme'
56
import { defineToolComponent } from './types'
@@ -21,6 +22,15 @@ const ReadFilesSimpleToolCallItem = ({
2122
}: ReadFilesSimpleToolCallItemProps) => {
2223
const theme = useTheme()
2324
const bulletChar = '• '
25+
const hasBranch = !!branchChar && branchChar.length > 0
26+
const toggleIndicator = '▸ '
27+
const toggleWidth = stringWidth(toggleIndicator)
28+
const branchHead = hasBranch ? branchChar.replace(/\s+$/, '') : ''
29+
const dashFiller = '─'.repeat(toggleWidth)
30+
const labelPrefix = hasBranch
31+
? `${branchHead}${dashFiller} `
32+
: branchChar || bulletChar
33+
const baseIndentWidth = stringWidth(labelPrefix) + stringWidth(name + ' ')
2434

2535
// Split files into two groups
2636
const firstFilePath = filePaths[0]
@@ -34,7 +44,7 @@ const ReadFilesSimpleToolCallItem = ({
3444
style={{ flexDirection: 'row', alignItems: 'center', width: '100%' }}
3545
>
3646
<text style={{ wrapMode: 'word' }}>
37-
<span fg={theme.foreground}>{branchChar || bulletChar}</span>
47+
<span fg={theme.foreground}>{labelPrefix}</span>
3848
<span fg={theme.foreground} attributes={TextAttributes.BOLD}>
3949
{name}
4050
</span>
@@ -54,7 +64,7 @@ const ReadFilesSimpleToolCallItem = ({
5464
flexDirection: 'row',
5565
alignItems: 'center',
5666
width: '100%',
57-
paddingLeft: 7,
67+
paddingLeft: baseIndentWidth,
5868
}}
5969
>
6070
<text style={{ wrapMode: 'word' }}>
@@ -70,7 +80,7 @@ const ReadFilesSimpleToolCallItem = ({
7080
flexDirection: 'row',
7181
alignItems: 'center',
7282
width: '100%',
73-
paddingLeft: 7,
83+
paddingLeft: baseIndentWidth,
7484
}}
7585
>
7686
<text style={{ wrapMode: 'word' }}>

0 commit comments

Comments
 (0)