Skip to content

Commit 246209a

Browse files
committed
Refine branch item UI and add cursor blinking to multiline input
Simplified branch visualization by removing tree-style branch characters (├─, └─) and their associated indentation logic. Added consistent vertical margins to branch items for cleaner spacing. Enhanced multiline input with idle-based cursor blinking that activates after 6 seconds of inactivity, providing better visual feedback while maintaining a clean appearance during active typing. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent ce32c18 commit 246209a

File tree

5 files changed

+208
-73
lines changed

5 files changed

+208
-73
lines changed

cli/src/chat.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,18 +1034,12 @@ export const App = ({ initialPrompt }: { initialPrompt?: string } = {}) => {
10341034
indicatorLength = 15 // " connecting... "
10351035
}
10361036

1037-
let queueLength = 0
1038-
if (queuePreview) {
1039-
// <span> </span> + <span>{' '}{queuePreview}{' '}</span>
1040-
queueLength = 1 + 1 + queuePreview.length + 1
1041-
}
1042-
10431037
// The components are laid out as:
1044-
// [left dashes] [space] [indicator] [queueBlock if any] [space] [right dashes]
1045-
// Total content: leftDashes + 1 + indicator + queue + 1 + rightDashes
1038+
// [left dashes] [space] [indicator] [space] [right dashes]
1039+
// Total content: leftDashes + 1 + indicator + 1 + rightDashes
10461040
const leftDashCount = Math.floor(composerWidth * 0.1)
1047-
return leftDashCount + 1 + indicatorLength + queueLength + 1
1048-
}, [clipboardMessage, isWaitingForResponse, queuePreview, composerWidth])
1041+
return leftDashCount + 1 + indicatorLength + 1
1042+
}, [clipboardMessage, isWaitingForResponse, composerWidth])
10491043

10501044
const shouldShowIdleSuggestions = false
10511045

@@ -1245,7 +1239,29 @@ export const App = ({ initialPrompt }: { initialPrompt?: string } = {}) => {
12451239
backgroundColor: theme.panelBg,
12461240
}}
12471241
>
1248-
<text wrap={false} style={{ fg: theme.statusSecondary }}>
1242+
{queuePreview && (
1243+
<text
1244+
wrap={false}
1245+
style={{
1246+
fg: theme.statusSecondary,
1247+
marginTop: hasStatus ? 0 : 1,
1248+
marginBottom: 0,
1249+
}}
1250+
>
1251+
<span fg={theme.statusSecondary} bg={theme.inputFocusedBg}>
1252+
{' '}
1253+
{queuePreview}
1254+
{' '}
1255+
</span>
1256+
</text>
1257+
)}
1258+
<text
1259+
wrap={false}
1260+
style={{
1261+
fg: theme.statusSecondary,
1262+
marginTop: hasStatus ? 1 : 0,
1263+
}}
1264+
>
12491265
{hasStatus ? (
12501266
<>
12511267
<span>{`${'─'.repeat(Math.max(0, Math.floor(composerWidth * 0.1)))}`}</span>
@@ -1261,18 +1277,6 @@ export const App = ({ initialPrompt }: { initialPrompt?: string } = {}) => {
12611277
) : (
12621278
<ShimmerText text=" connecting... " />
12631279
)}
1264-
{queuePreview && (
1265-
<>
1266-
<span> </span>
1267-
<span
1268-
fg={theme.statusSecondary}
1269-
bg={theme.inputFocusedBg}
1270-
>
1271-
{' '}
1272-
{queuePreview}{' '}
1273-
</span>
1274-
</>
1275-
)}
12761280
<span> </span>
12771281
<span>{`${'─'.repeat(Math.max(0, composerWidth - statusTextLength))}`}</span>
12781282
</>

cli/src/components/branch-item.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ interface BranchItemProps {
1616
content: ReactNode | (() => ReactNode)
1717
isCollapsed: boolean
1818
isStreaming: boolean
19-
branchChar: string
2019
streamingPreview: string
2120
finishedPreview: string
2221
theme: ChatTheme
2322
onToggle: () => void
2423
quickActions?: BranchQuickAction[]
2524
variant?: 'agent' | 'tool'
25+
density?: 'default' | 'compact'
2626
}
2727

2828
const BRANCH_BORDER_CHARS = {
@@ -38,25 +38,25 @@ const BRANCH_BORDER_CHARS = {
3838
rightT: '┤',
3939
cross: '┼',
4040
} as const
41+
const BRANCH_VERTICAL_MARGIN = 1
4142

4243
export const BranchItem = ({
4344
name,
4445
content,
4546
isCollapsed,
4647
isStreaming,
47-
branchChar,
4848
streamingPreview,
4949
finishedPreview,
5050
theme,
5151
onToggle,
5252
quickActions = [],
5353
variant = 'agent',
54+
density = 'default',
5455
}: BranchItemProps) => {
55-
const indentPrefix = branchChar ? branchChar.replace(/./g, ' ') : ''
56+
const isCompact = density === 'compact'
5657
const baseCornerColor = theme.agentPrefix
5758
const hasContent = typeof content === 'function' ? true : !!content
5859
const shouldMergeHeader = !isCollapsed && hasContent
59-
const outerPaddingRight = branchChar ? 1 : 0
6060
const baseHeaderBackground = isCollapsed
6161
? theme.agentResponseCount
6262
: theme.agentPrefix
@@ -68,6 +68,12 @@ export const BranchItem = ({
6868
: baseHeaderBackground
6969
const contentBackground =
7070
variant === 'tool' ? theme.toolBg ?? theme.agentContentBg : undefined
71+
const headerPaddingLeft = shouldMergeHeader ? 0 : isCompact ? 0 : 1
72+
const headerPaddingRight = isCompact ? 1 : 1
73+
const mergedHeaderPaddingLeft = isCompact ? 0 : 1
74+
const mergedHeaderPaddingRight = isCompact ? 1 : 1
75+
const contentPaddingLeft = isCompact ? 0 : 1
76+
const collapsePaddingLeft = shouldMergeHeader ? 0 : isCompact ? 0 : 1
7177

7278
const handleActionSelect = (action: BranchQuickAction, event: any): void => {
7379
if (event && typeof event.stopPropagation === 'function') {
@@ -174,12 +180,11 @@ export const BranchItem = ({
174180
flexDirection: 'row',
175181
flexShrink: 0,
176182
alignSelf: isCollapsed ? 'flex-start' : 'stretch',
177-
paddingRight: outerPaddingRight,
178-
marginTop: 0,
179-
marginBottom: 0,
183+
paddingRight: 0,
184+
marginTop: BRANCH_VERTICAL_MARGIN,
185+
marginBottom: BRANCH_VERTICAL_MARGIN,
180186
}}
181187
>
182-
<text wrap={false}>{indentPrefix}</text>
183188
<box
184189
style={{
185190
flexDirection: 'column',
@@ -198,8 +203,8 @@ export const BranchItem = ({
198203
justifyContent: 'space-between',
199204
alignSelf: isCollapsed ? 'flex-start' : 'stretch',
200205
backgroundColor: headerBackground,
201-
paddingLeft: 2,
202-
paddingRight: 2,
206+
paddingLeft: headerPaddingLeft,
207+
paddingRight: headerPaddingRight,
203208
paddingTop: 0,
204209
paddingBottom: 0,
205210
marginTop: 0,
@@ -294,7 +299,7 @@ export const BranchItem = ({
294299
border: ['top', 'left', 'right', 'bottom'],
295300
customBorderChars: BRANCH_BORDER_CHARS,
296301
borderColor: effectiveCornerColor,
297-
paddingLeft: 1,
302+
paddingLeft: contentPaddingLeft,
298303
paddingRight: 2,
299304
paddingTop: shouldMergeHeader ? 0 : 1,
300305
paddingBottom: 1,
@@ -308,8 +313,8 @@ export const BranchItem = ({
308313
alignItems: 'center',
309314
justifyContent: 'space-between',
310315
backgroundColor: theme.agentPrefix,
311-
paddingLeft: 2,
312-
paddingRight: 2,
316+
paddingLeft: mergedHeaderPaddingLeft,
317+
paddingRight: mergedHeaderPaddingRight,
313318
paddingTop: 0,
314319
paddingBottom: 0,
315320
marginBottom: 1,
@@ -378,7 +383,7 @@ export const BranchItem = ({
378383
flexDirection: 'row',
379384
alignItems: 'center',
380385
marginTop: 0,
381-
paddingLeft: 1,
386+
paddingLeft: collapsePaddingLeft,
382387
paddingRight: 2,
383388
}}
384389
onMouseDown={onToggle}

cli/src/components/message-block.tsx

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,6 @@ export const MessageBlock = ({
211211
? renderMarkdown(fullContent, agentMarkdownOptions)
212212
: fullContent
213213

214-
const nextBlock = blocks[idx + 1]
215-
const isLastBranch = !nextBlock || nextBlock.type === 'text'
216-
const branchChar = isLastBranch ? '└─ ' : '├─ '
217-
218214
return (
219215
<box
220216
key={`${messageId}-tool-${block.toolCallId}`}
@@ -225,7 +221,6 @@ export const MessageBlock = ({
225221
content={displayContent}
226222
isCollapsed={isCollapsed}
227223
isStreaming={isStreaming}
228-
branchChar={branchChar}
229224
streamingPreview={streamingPreview}
230225
finishedPreview={finishedPreview}
231226
theme={theme}
@@ -248,8 +243,14 @@ export const MessageBlock = ({
248243
.filter((line) => line.trim())
249244
const firstLine = lines[0] || ''
250245

246+
const sanitizedFirstLine = sanitizePreview(firstLine)
247+
const sanitizedPrompt = block.initialPrompt
248+
? sanitizePreview(block.initialPrompt)
249+
: ''
251250
const streamingPreview = isStreaming
252-
? firstLine.replace(/[#*_`~\[\]()]/g, '').trim() + '...'
251+
? sanitizedFirstLine.length > 0
252+
? `${sanitizedFirstLine}...`
253+
: sanitizedPrompt
253254
: ''
254255

255256
const finishedPreview =
@@ -300,8 +301,6 @@ export const MessageBlock = ({
300301
wrap
301302
style={{ fg: theme.agentText }}
302303
>
303-
spawn a file picker and then tell me your favorite file
304-
in this codebase
305304
{renderedContent}
306305
</text>,
307306
)
@@ -395,7 +394,7 @@ export const MessageBlock = ({
395394
content={nestedDisplayContent}
396395
isCollapsed={isNestedCollapsed}
397396
isStreaming={isNestedStreaming}
398-
branchChar=""
397+
density="compact"
399398
streamingPreview={nestedStreamingPreview}
400399
finishedPreview={nestedFinishedPreview}
401400
theme={theme}
@@ -429,9 +428,15 @@ export const MessageBlock = ({
429428
.filter((line) => line.trim())
430429
const nestedFirstLine = nestedLines[0] || ''
431430

431+
const nestedSanitizedFirstLine =
432+
sanitizePreview(nestedFirstLine)
433+
const nestedSanitizedPrompt = nestedBlock.initialPrompt
434+
? sanitizePreview(nestedBlock.initialPrompt)
435+
: ''
432436
const nestedStreamingPreview = nestedIsStreaming
433-
? nestedFirstLine.replace(/[#*_`~\[\]()]/g, '').trim() +
434-
'...'
437+
? nestedSanitizedFirstLine.length > 0
438+
? `${nestedSanitizedFirstLine}...`
439+
: nestedSanitizedPrompt
435440
: ''
436441

437442
const nestedFinishedPreview =
@@ -458,12 +463,18 @@ export const MessageBlock = ({
458463
const nestedChildNodes =
459464
!nestedIsCollapsed && nestedBranchBlocks.length > 0
460465
? nestedBranchBlocks
461-
.map((childBlock, childIdx) =>
462-
renderNestedBranchItem(
466+
.map((childBlock, childIdx) => {
467+
const childKey = `${keyPrefix}-child-${childIdx}`
468+
const node = renderNestedBranchItem(
463469
childBlock,
464-
`${keyPrefix}-child-${childIdx}`,
465-
),
466-
)
470+
childKey,
471+
)
472+
return node ? (
473+
<React.Fragment key={childKey}>
474+
{node}
475+
</React.Fragment>
476+
) : null
477+
})
467478
.filter((node): node is ReactNode => node !== null)
468479
: []
469480

@@ -494,7 +505,7 @@ export const MessageBlock = ({
494505
content={nestedDisplayContent}
495506
isCollapsed={nestedIsCollapsed}
496507
isStreaming={nestedIsStreaming}
497-
branchChar=""
508+
density="compact"
498509
streamingPreview={nestedStreamingPreview}
499510
finishedPreview={nestedFinishedPreview}
500511
theme={theme}
@@ -526,12 +537,13 @@ export const MessageBlock = ({
526537
const nestedBranchNodes =
527538
!isCollapsed && branchableChildren.length > 0
528539
? branchableChildren
529-
.map((nestedBlock, nestedIdx) =>
530-
renderNestedBranchItem(
531-
nestedBlock,
532-
`${messageId}-agent-${block.agentId}-nested-${nestedIdx}`,
533-
),
534-
)
540+
.map((nestedBlock, nestedIdx) => {
541+
const key = `${messageId}-agent-${block.agentId}-nested-${nestedIdx}`
542+
const node = renderNestedBranchItem(nestedBlock, key)
543+
return node ? (
544+
<React.Fragment key={key}>{node}</React.Fragment>
545+
) : null
546+
})
535547
.filter((node): node is ReactNode => node !== null)
536548
: []
537549

@@ -549,10 +561,6 @@ export const MessageBlock = ({
549561
</box>
550562
)
551563

552-
const nextBlock = blocks[idx + 1]
553-
const isLastBranch = !nextBlock || nextBlock.type === 'text'
554-
const branchChar = isLastBranch ? '└─ ' : '├─ '
555-
556564
return (
557565
<box
558566
key={`${messageId}-agent-${block.agentId}`}
@@ -564,7 +572,6 @@ export const MessageBlock = ({
564572
content={displayContent}
565573
isCollapsed={isCollapsed}
566574
isStreaming={isStreaming}
567-
branchChar={branchChar}
568575
streamingPreview={streamingPreview}
569576
finishedPreview={finishedPreview}
570577
theme={theme}

0 commit comments

Comments
 (0)