From 63c7a9b5f8ec7fc8a1f2657aa8f49a72c3015ffb Mon Sep 17 00:00:00 2001 From: Raunak Raj <71929976+bajrangCoder@users.noreply.github.com> Date: Sat, 20 Jun 2026 00:01:28 +0530 Subject: [PATCH] feat: ui part of custom session replay cli changes --- src/pages/chat/index.tsx | 81 +++++++++++++++++++++++++++++++++++++--- src/state/acp.ts | 16 ++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx index 4367e53..7c44862 100644 --- a/src/pages/chat/index.tsx +++ b/src/pages/chat/index.tsx @@ -41,6 +41,7 @@ import { acpCancel, acpCreateSession, acpDetachSession, + acpLoadOlderMessages, acpPermissionReply, acpPrompt, acpSetConfigOption, @@ -150,6 +151,7 @@ export default function ChatConversationPage({ const [visibleCount, setVisibleCount] = useState(PAGE_SIZE); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); + const [hasOlderHistory, setHasOlderHistory] = useState(false); const [scrollAdjust, setScrollAdjust] = useState(0); const [syncing, setSyncing] = useState(false); const [activeSessionId, setActiveSessionId] = useState(sessionId); @@ -215,6 +217,7 @@ export default function ChatConversationPage({ const streamingUserIdRef = useRef(null); const sentTurnStartRef = useRef(0); const allMessagesLengthRef = useRef(0); + const oldestHistoryCursorRef = useRef(null); const upsertAcpMessageRef = useRef<(msg: AcpMessage) => void>(() => {}); connectionStatusRef.current = connectionStatus; @@ -243,7 +246,9 @@ export default function ChatConversationPage({ allMessagesLengthRef.current = allMessages.length; }, [allMessages.length]); - const hasMore = allMessages.length > visibleCount; + const hasBufferedMessages = allMessages.length > visibleCount; + const hasMore = hasBufferedMessages || hasOlderHistory; + const usesNativeHistory = agentId === "codex" || agentId === "opencode"; const promptSuggestions = useMemo( () => composerTrigger?.trigger === "/" @@ -314,15 +319,65 @@ export default function ChatConversationPage({ [scrollToBottomNow], ); - const triggerLoadMore = useCallback(() => { + const triggerLoadMore = useCallback(async () => { if (!historyScrollReady || !hasMore || loadingMore || !scrollRef.current) { return; } prevScrollHeightRef.current = scrollRef.current.scrollHeight; setLoadingMore(true); - setVisibleCount((c) => c + PAGE_SIZE); - setScrollAdjust((n) => n + 1); - }, [hasMore, historyScrollReady, loadingMore]); + if (hasBufferedMessages) { + setVisibleCount((count) => count + PAGE_SIZE); + setScrollAdjust((value) => value + 1); + return; + } + + const cursor = oldestHistoryCursorRef.current; + const targetSessionId = activeSessionId || sessionId; + if (!hasOlderHistory || !cursor || !targetSessionId) { + setHasOlderHistory(false); + setLoadingMore(false); + return; + } + + try { + const older = await acpLoadOlderMessages( + agentId, + targetSessionId, + resolvedWorkspacePath || ".", + cursor, + PAGE_SIZE, + ); + if (older.length === 0) { + setHasOlderHistory(false); + setLoadingMore(false); + return; + } + oldestHistoryCursorRef.current = older[0]?.id ?? null; + setHasOlderHistory(older.length >= PAGE_SIZE); + setVisibleCount((count) => count + older.length); + setAllMessages((current) => { + const currentIds = new Set(current.map((message) => message.id)); + const uniqueOlder = older.filter( + (message) => !currentIds.has(message.id), + ); + return uniqueOlder.length > 0 ? [...uniqueOlder, ...current] : current; + }); + setScrollAdjust((value) => value + 1); + } catch (loadError) { + setError((loadError as Error).message); + setLoadingMore(false); + } + }, [ + activeSessionId, + agentId, + hasBufferedMessages, + hasMore, + hasOlderHistory, + historyScrollReady, + loadingMore, + resolvedWorkspacePath, + sessionId, + ]); const handleSessionStatus = useCallback( (properties: Record) => { @@ -378,6 +433,8 @@ export default function ChatConversationPage({ if (Array.isArray(messages)) { setAllMessages(messages as AcpMessage[]); setVisibleCount(PAGE_SIZE); + oldestHistoryCursorRef.current = messages[0]?.id ?? null; + setHasOlderHistory(usesNativeHistory && messages.length >= PAGE_SIZE); stickToBottomRef.current = true; requestAnimationFrame(() => scrollToBottomNow(true)); } @@ -526,12 +583,16 @@ export default function ChatConversationPage({ stickToBottomRef.current = true; setPendingPermissions([]); setHistoryScrollReady(false); + setHasOlderHistory(false); + oldestHistoryCursorRef.current = null; }, [sessionId]); useEffect(() => { if (connectionStatus !== "connected") return; if (createOnFirstMessage) { setAllMessages([]); + setHasOlderHistory(false); + oldestHistoryCursorRef.current = null; setVisibleCount(PAGE_SIZE); stickToBottomRef.current = true; setHistoryScrollReady(true); @@ -640,6 +701,10 @@ export default function ChatConversationPage({ setConfigOptions(result.configOptions); setAvailableCommands(result.availableCommands); setAllMessages(result.messages); + oldestHistoryCursorRef.current = result.messages[0]?.id ?? null; + setHasOlderHistory( + usesNativeHistory && result.messages.length >= PAGE_SIZE, + ); attachedSessionIdRef.current = sessionId; attachedRevisionRef.current = result.revision; attachReadyRef.current = true; @@ -661,7 +726,6 @@ export default function ChatConversationPage({ applyRevisionedSessionEvent(event); } setLoading(false); - setSyncing(false); }) .catch((err) => { if (!cancelled) { @@ -691,6 +755,7 @@ export default function ChatConversationPage({ agentAvailable, sessionId, resolvedWorkspacePath, + usesNativeHistory, ]); // Scroll to bottom after React commits new messages or streaming tokens @@ -1369,6 +1434,10 @@ export default function ChatConversationPage({ setConfigOptions(result.configOptions); setAvailableCommands(result.availableCommands); setAllMessages(result.messages); + oldestHistoryCursorRef.current = result.messages[0]?.id ?? null; + setHasOlderHistory( + usesNativeHistory && result.messages.length >= PAGE_SIZE, + ); setSyncing(Boolean(result.syncing)); cleanupRef.current?.(); attachReadyRef.current = true; diff --git a/src/state/acp.ts b/src/state/acp.ts index f05ef66..6f84c5f 100644 --- a/src/state/acp.ts +++ b/src/state/acp.ts @@ -5,6 +5,7 @@ import { type AiAttachmentWriteResultMsg, type AiBackend, type AiEventMsg, + type AiMessagesListResultMsg, type AiPermissionReplyAckMsg, type AiPromptMsg, type AiSession, @@ -281,6 +282,21 @@ export async function acpAttachSession( }; } +export async function acpLoadOlderMessages( + agentId: AiBackend, + sessionId: string, + cwd: string, + cursor: string, + limit: number, +): Promise { + const result = await sendRequest({ + type: MsgType.AI_MESSAGES_LIST, + data: { backend: agentId, sessionId, cwd, cursor, limit }, + } as SendableMsg); + assertNoError(result); + return (result.data?.messages ?? []) as AcpMessage[]; +} + export async function acpDetachSession( agentId: AiBackend, sessionId: string,