Skip to content

Commit 7a0a654

Browse files
committed
Go to a much more compact follow up steps UI
1 parent 6d71747 commit 7a0a654

File tree

1 file changed

+78
-73
lines changed

1 file changed

+78
-73
lines changed

cli/src/components/tools/suggest-followups.tsx

Lines changed: 78 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { useCallback, useState } from 'react'
22
import { TextAttributes } from '@opentui/core'
33

4-
import { Button } from '../button'
54
import { defineToolComponent } from './types'
65
import { useTheme } from '../../hooks/use-theme'
76
import { useChatStore } from '../../state/chat-store'
@@ -10,26 +9,24 @@ import { useTerminalDimensions } from '../../hooks/use-terminal-dimensions'
109
import type { ToolRenderConfig } from './types'
1110
import type { SuggestedFollowup } from '../../state/chat-store'
1211

13-
interface FollowupCardProps {
12+
interface FollowupLineProps {
1413
followup: SuggestedFollowup
1514
index: number
1615
isClicked: boolean
17-
isStacked: boolean
1816
onSendFollowup: (prompt: string, index: number) => void
1917
}
2018

21-
const FollowupCard = ({
19+
const FollowupLine = ({
2220
followup,
2321
index,
2422
isClicked,
25-
isStacked,
2623
onSendFollowup,
27-
}: FollowupCardProps) => {
24+
}: FollowupLineProps) => {
2825
const theme = useTheme()
26+
const { terminalWidth } = useTerminalDimensions()
2927
const [isHovered, setIsHovered] = useState(false)
3028

3129
const handleClick = useCallback(() => {
32-
// Don't allow clicking already-selected followups
3330
if (isClicked) return
3431
onSendFollowup(followup.prompt, index)
3532
}, [followup.prompt, index, onSendFollowup, isClicked])
@@ -38,52 +35,79 @@ const FollowupCard = ({
3835
const handleMouseOut = useCallback(() => setIsHovered(false), [])
3936

4037
const hasLabel = Boolean(followup.label)
38+
// "→ " = 2 chars (icon + space), " · " separator = 3 chars, "…" = 1 char
39+
const iconWidth = 2
40+
const separatorWidth = hasLabel ? 3 : 0
41+
const ellipsisWidth = 1
42+
const maxWidth = terminalWidth - 6 // Extra margin for safety
43+
44+
// Build the display text with label and prompt
45+
let labelText = followup.label || ''
46+
let promptText = followup.prompt
47+
48+
// Calculate available space
49+
const availableForContent = maxWidth - iconWidth
50+
51+
if (hasLabel) {
52+
// Show: label · prompt (truncated)
53+
const labelWithSeparator = labelText.length + separatorWidth
54+
const totalLength = labelWithSeparator + promptText.length
55+
56+
if (totalLength > availableForContent) {
57+
// Truncate prompt to fit
58+
const availableForPrompt = availableForContent - labelWithSeparator - ellipsisWidth
59+
if (availableForPrompt > 0) {
60+
promptText = promptText.slice(0, availableForPrompt) + '…'
61+
} else {
62+
// Not enough space for prompt, just show label truncated
63+
promptText = ''
64+
if (labelText.length > availableForContent - ellipsisWidth) {
65+
labelText = labelText.slice(0, availableForContent - ellipsisWidth) + '…'
66+
}
67+
}
68+
}
69+
} else {
70+
// No label, just show prompt (truncated)
71+
if (promptText.length > availableForContent) {
72+
promptText = promptText.slice(0, availableForContent - ellipsisWidth) + '…'
73+
}
74+
}
4175

4276
// Determine colors based on state
43-
const borderColor = isClicked
77+
const iconColor = isClicked
4478
? theme.success
4579
: isHovered
4680
? theme.primary
47-
: theme.border
48-
const labelColor = isClicked ? theme.muted : theme.secondary
49-
const promptColor = isClicked ? theme.muted : theme.foreground
81+
: theme.muted
82+
const labelColor = isClicked
83+
? theme.muted
84+
: isHovered
85+
? theme.primary
86+
: theme.foreground
87+
const promptColor = isClicked
88+
? theme.muted
89+
: isHovered
90+
? theme.primary
91+
: theme.muted
5092

5193
return (
52-
<Button
53-
onClick={handleClick}
94+
<box
95+
onMouseDown={handleClick}
5496
onMouseOver={handleMouseOver}
5597
onMouseOut={handleMouseOut}
56-
style={{
57-
paddingLeft: 2,
58-
paddingRight: 2,
59-
paddingTop: 0,
60-
paddingBottom: 0,
61-
...(isStacked ? { width: '100%' } : { flexGrow: 1, flexShrink: 1 }),
62-
borderColor,
63-
}}
6498
>
65-
<box style={{ flexDirection: 'column' }}>
66-
{hasLabel && (
67-
<text
68-
style={{
69-
fg: labelColor,
70-
}}
71-
attributes={TextAttributes.BOLD}
72-
>
73-
{isClicked ? <span fg={theme.success}></span> : <span></span>}
74-
<span>{followup.label}</span>
75-
</text>
99+
<text selectable={false}>
100+
<span fg={iconColor}>{isClicked ? '✓' : '→'}</span>
101+
<span fg={labelColor} attributes={isHovered ? TextAttributes.UNDERLINE : undefined}>
102+
{' '}{hasLabel ? labelText : promptText}
103+
</span>
104+
{hasLabel && promptText && (
105+
<span fg={promptColor}>
106+
{' · '}{promptText}
107+
</span>
76108
)}
77-
<text
78-
style={{
79-
fg: promptColor,
80-
}}
81-
>
82-
{!hasLabel && isClicked && <span fg={theme.success}></span>}
83-
<span>{followup.prompt}</span>
84-
</text>
85-
</box>
86-
</Button>
109+
</text>
110+
</box>
87111
)
88112
}
89113

@@ -93,16 +117,12 @@ interface SuggestFollowupsItemProps {
93117
onSendFollowup: (prompt: string, index: number) => void
94118
}
95119

96-
// Threshold width to switch between horizontal and stacked layouts
97-
const WIDE_SCREEN_THRESHOLD = 100
98-
99120
const SuggestFollowupsItem = ({
100121
toolCallId,
101122
followups,
102123
onSendFollowup,
103124
}: SuggestFollowupsItemProps) => {
104125
const theme = useTheme()
105-
const { terminalWidth } = useTerminalDimensions()
106126
const suggestedFollowups = useChatStore((state) => state.suggestedFollowups)
107127

108128
// Get clicked indices for this specific tool call
@@ -111,35 +131,20 @@ const SuggestFollowupsItem = ({
111131
? suggestedFollowups.clickedIndices
112132
: new Set<number>()
113133

114-
// Use stacked layout on narrow screens
115-
const isStacked = terminalWidth < WIDE_SCREEN_THRESHOLD
116-
117134
return (
118-
<box
119-
style={{
120-
flexDirection: 'column',
121-
gap: 1,
122-
}}
123-
>
124-
<text style={{ fg: theme.primary }} attributes={TextAttributes.BOLD}>
125-
Suggested next steps:
135+
<box style={{ flexDirection: 'column' }}>
136+
<text style={{ fg: theme.muted }}>
137+
Next steps:
126138
</text>
127-
<box
128-
style={{
129-
flexDirection: isStacked ? 'column' : 'row',
130-
}}
131-
>
132-
{followups.map((followup, index) => (
133-
<FollowupCard
134-
key={`followup-${index}`}
135-
followup={followup}
136-
index={index}
137-
isClicked={clickedIndices.has(index)}
138-
isStacked={isStacked}
139-
onSendFollowup={onSendFollowup}
140-
/>
141-
))}
142-
</box>
139+
{followups.map((followup, index) => (
140+
<FollowupLine
141+
key={`followup-${index}`}
142+
followup={followup}
143+
index={index}
144+
isClicked={clickedIndices.has(index)}
145+
onSendFollowup={onSendFollowup}
146+
/>
147+
))}
143148
</box>
144149
)
145150
}

0 commit comments

Comments
 (0)