@@ -3,16 +3,23 @@ import type {
33 SessionNotification ,
44} from "@agentclientprotocol/sdk" ;
55import type { SessionUpdate , ToolCall } from "@features/sessions/types" ;
6- import { XCircle } from "@phosphor-icons/react" ;
7- import { Box , Flex , Text } from "@radix-ui/themes" ;
6+ import { ArrowDown , XCircle } from "@phosphor-icons/react" ;
7+ import { Box , Button , Flex , Text } from "@radix-ui/themes" ;
88import {
99 type AcpMessage ,
1010 isJsonRpcNotification ,
1111 isJsonRpcRequest ,
1212 isJsonRpcResponse ,
1313 type UserShellExecuteParams ,
1414} from "@shared/types/session-events" ;
15- import { memo , useLayoutEffect , useMemo , useRef } from "react" ;
15+ import {
16+ memo ,
17+ useCallback ,
18+ useLayoutEffect ,
19+ useMemo ,
20+ useRef ,
21+ useState ,
22+ } from "react" ;
1623import { GitActionMessage , parseGitActionMessage } from "./GitActionMessage" ;
1724import { GitActionResult } from "./GitActionResult" ;
1825import { SessionFooter } from "./SessionFooter" ;
@@ -47,6 +54,9 @@ interface ConversationViewProps {
4754 isCloud ?: boolean ;
4855}
4956
57+ const SCROLL_THRESHOLD = 100 ;
58+ const SHOW_BUTTON_THRESHOLD = 300 ;
59+
5060export function ConversationView ( {
5161 events,
5262 isPromptPending,
@@ -59,17 +69,18 @@ export function ConversationView({
5969
6070 const isNearBottomRef = useRef ( true ) ;
6171 const prevItemsLengthRef = useRef ( 0 ) ;
72+ const [ showScrollButton , setShowScrollButton ] = useState ( false ) ;
6273
6374 // Update isNearBottom on scroll
6475 useLayoutEffect ( ( ) => {
6576 const el = scrollRef . current ;
6677 if ( ! el ) return ;
6778
6879 const handleScroll = ( ) => {
69- const threshold = 100 ;
7080 const distanceFromBottom =
7181 el . scrollHeight - el . scrollTop - el . clientHeight ;
72- isNearBottomRef . current = distanceFromBottom <= threshold ;
82+ isNearBottomRef . current = distanceFromBottom <= SCROLL_THRESHOLD ;
83+ setShowScrollButton ( distanceFromBottom > SHOW_BUTTON_THRESHOLD ) ;
7384 } ;
7485
7586 el . addEventListener ( "scroll" , handleScroll ) ;
@@ -89,32 +100,49 @@ export function ConversationView({
89100 }
90101 } , [ items ] ) ;
91102
103+ const scrollToBottom = useCallback ( ( ) => {
104+ const el = scrollRef . current ;
105+ if ( el ) {
106+ el . scrollTo ( { top : el . scrollHeight , behavior : "smooth" } ) ;
107+ }
108+ } , [ ] ) ;
109+
92110 return (
93- < div
94- ref = { scrollRef }
95- className = "scrollbar-hide flex-1 overflow-auto bg-white p-2 pb-16 dark:bg-gray-1"
96- >
97- < div className = "flex flex-col gap-3" >
98- { items . map ( ( item ) =>
99- item . type === "turn" ? (
100- < TurnView
101- key = { item . id }
102- turn = { item }
103- repoPath = { repoPath }
104- isCloud = { isCloud }
105- />
106- ) : (
107- < UserShellExecuteView key = { item . id } item = { item } />
108- ) ,
109- ) }
111+ < div className = "relative flex-1" >
112+ < div
113+ ref = { scrollRef }
114+ className = "scrollbar-hide absolute inset-0 overflow-auto bg-white p-2 pb-16 dark:bg-gray-1"
115+ >
116+ < div className = "flex flex-col gap-3" >
117+ { items . map ( ( item ) =>
118+ item . type === "turn" ? (
119+ < TurnView
120+ key = { item . id }
121+ turn = { item }
122+ repoPath = { repoPath }
123+ isCloud = { isCloud }
124+ />
125+ ) : (
126+ < UserShellExecuteView key = { item . id } item = { item } />
127+ ) ,
128+ ) }
129+ </ div >
130+ < SessionFooter
131+ isPromptPending = { isPromptPending }
132+ lastGenerationDuration = {
133+ lastTurn ?. isComplete ? lastTurn . durationMs : null
134+ }
135+ lastStopReason = { lastTurn ?. stopReason }
136+ />
110137 </ div >
111- < SessionFooter
112- isPromptPending = { isPromptPending }
113- lastGenerationDuration = {
114- lastTurn ?. isComplete ? lastTurn . durationMs : null
115- }
116- lastStopReason = { lastTurn ?. stopReason }
117- />
138+ { showScrollButton && (
139+ < Box className = "absolute right-4 bottom-4 z-10" >
140+ < Button size = "1" variant = "solid" onClick = { scrollToBottom } >
141+ < ArrowDown size = { 14 } weight = "bold" />
142+ Scroll to bottom
143+ </ Button >
144+ </ Box >
145+ ) }
118146 </ div >
119147 ) ;
120148}
0 commit comments