Skip to content

Commit 2a13dc9

Browse files
committed
cli: Add message pagination to improve performance with long conversations
1 parent ced72fb commit 2a13dc9

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

cli/src/chat.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { routeUserPrompt, addBashMessageToHistory } from './commands/router'
1414
import type { CommandResult } from './commands/command-registry'
1515
import { AnnouncementBanner } from './components/announcement-banner'
1616
import { ChatInputBar } from './components/chat-input-bar'
17+
import { LoadPreviousButton } from './components/load-previous-button'
1718
import { MessageWithAgents } from './components/message-with-agents'
1819
import { PendingBashMessage } from './components/pending-bash-message'
1920
import { StatusBar } from './components/status-bar'
@@ -112,6 +113,10 @@ export const Chat = ({
112113
const [hasOverflow, setHasOverflow] = useState(false)
113114
const hasOverflowRef = useRef(false)
114115

116+
// Message pagination - show last N messages with "Load previous" button
117+
const MESSAGE_BATCH_SIZE = 15
118+
const [visibleMessageCount, setVisibleMessageCount] = useState(MESSAGE_BATCH_SIZE)
119+
115120
const queryClient = useQueryClient()
116121
const [, startUiTransition] = useTransition()
117122

@@ -242,6 +247,13 @@ export const Chat = ({
242247
activeSubagentsRef.current = activeSubagents
243248
}, [activeSubagents])
244249

250+
// Reset visible message count when messages are cleared or conversation changes
251+
useEffect(() => {
252+
if (messages.length <= MESSAGE_BATCH_SIZE) {
253+
setVisibleMessageCount(MESSAGE_BATCH_SIZE)
254+
}
255+
}, [messages.length])
256+
245257
const isUserCollapsingRef = useRef<boolean>(false)
246258

247259
const handleCollapseToggle = useCallback(
@@ -1235,6 +1247,20 @@ export const Chat = ({
12351247
[messages],
12361248
)
12371249

1250+
// Compute visible messages slice (from the end)
1251+
const visibleTopLevelMessages = useMemo(() => {
1252+
if (topLevelMessages.length <= visibleMessageCount) {
1253+
return topLevelMessages
1254+
}
1255+
return topLevelMessages.slice(-visibleMessageCount)
1256+
}, [topLevelMessages, visibleMessageCount])
1257+
1258+
const hiddenMessageCount = topLevelMessages.length - visibleTopLevelMessages.length
1259+
1260+
const handleLoadPreviousMessages = useCallback(() => {
1261+
setVisibleMessageCount((prev) => prev + MESSAGE_BATCH_SIZE)
1262+
}, [])
1263+
12381264
const modeConfig = getInputModeConfig(inputMode)
12391265
const hasSlashSuggestions =
12401266
slashContext.active &&
@@ -1351,8 +1377,14 @@ export const Chat = ({
13511377
)}
13521378

13531379
{headerContent}
1354-
{topLevelMessages.map((message, idx) => {
1355-
const isLast = idx === topLevelMessages.length - 1
1380+
{hiddenMessageCount > 0 && (
1381+
<LoadPreviousButton
1382+
hiddenCount={hiddenMessageCount}
1383+
onLoadMore={handleLoadPreviousMessages}
1384+
/>
1385+
)}
1386+
{visibleTopLevelMessages.map((message, idx) => {
1387+
const isLast = idx === visibleTopLevelMessages.length - 1
13561388
return (
13571389
<MessageWithAgents
13581390
key={message.id}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { memo, useState } from 'react'
2+
3+
import { Button } from './button'
4+
import { useTheme } from '../hooks/use-theme'
5+
import { BORDER_CHARS } from '../utils/ui-constants'
6+
7+
interface LoadPreviousButtonProps {
8+
hiddenCount: number
9+
onLoadMore: () => void
10+
}
11+
12+
export const LoadPreviousButton = memo(
13+
({ hiddenCount, onLoadMore }: LoadPreviousButtonProps) => {
14+
const theme = useTheme()
15+
const [isHovered, setIsHovered] = useState(false)
16+
17+
return (
18+
<box
19+
style={{
20+
flexDirection: 'row',
21+
justifyContent: 'center',
22+
paddingTop: 1,
23+
paddingBottom: 1,
24+
width: '100%',
25+
}}
26+
>
27+
<Button
28+
onClick={onLoadMore}
29+
onMouseOver={() => setIsHovered(true)}
30+
onMouseOut={() => setIsHovered(false)}
31+
border
32+
borderStyle="single"
33+
borderColor={isHovered ? theme.foreground : theme.border}
34+
customBorderChars={BORDER_CHARS}
35+
style={{
36+
paddingLeft: 2,
37+
paddingRight: 2,
38+
backgroundColor: 'transparent',
39+
}}
40+
>
41+
<text style={{ fg: isHovered ? theme.foreground : theme.muted }}>
42+
↑ Load previous messages
43+
</text>
44+
</Button>
45+
</box>
46+
)
47+
},
48+
)

0 commit comments

Comments
 (0)