@@ -123,6 +123,7 @@ export function Session() {
123123 . toSorted ( ( a , b ) => ( a . id < b . id ? - 1 : a . id > b . id ? 1 : 0 ) )
124124 } )
125125 const messages = createMemo ( ( ) => sync . data . message [ route . sessionID ] ?? [ ] )
126+ const paging = createMemo ( ( ) => sync . data . message_page [ route . sessionID ] )
126127 const permissions = createMemo ( ( ) => {
127128 if ( session ( ) ?. parentID ) return [ ]
128129 return children ( ) . flatMap ( ( x ) => sync . data . permission [ x . id ] ?? [ ] )
@@ -132,6 +133,53 @@ export function Session() {
132133 return children ( ) . flatMap ( ( x ) => sync . data . question [ x . id ] ?? [ ] )
133134 } )
134135
136+ const LOAD_MORE_THRESHOLD = 5
137+
138+ const loadOlder = ( ) => {
139+ const page = paging ( )
140+ if ( ! page ?. hasOlder || page . loading || ! scroll ) return
141+ if ( scroll . scrollTop > LOAD_MORE_THRESHOLD ) return
142+
143+ const anchor = ( ( ) => {
144+ const scrollTop = scroll . scrollTop
145+ const children = scroll . getChildren ( )
146+ for ( const child of children ) {
147+ if ( ! child . id ) continue
148+ if ( child . y + child . height > scrollTop ) {
149+ return { id : child . id , offset : scrollTop - child . y }
150+ }
151+ }
152+ return undefined
153+ } ) ( )
154+
155+ const height = scroll . scrollHeight
156+ const scrollTop = scroll . scrollTop
157+ sync . session . loadOlder ( route . sessionID ) . then ( ( ) => {
158+ queueMicrotask ( ( ) => {
159+ requestAnimationFrame ( ( ) => {
160+ if ( anchor ) {
161+ const child = scroll . getChildren ( ) . find ( ( item ) => item . id === anchor . id )
162+ if ( child ) {
163+ scroll . scrollTo ( child . y + anchor . offset )
164+ return
165+ }
166+ }
167+
168+ const delta = scroll . scrollHeight - height
169+ if ( delta > 0 ) scroll . scrollTo ( scrollTop + delta )
170+ } )
171+ } )
172+ } )
173+ }
174+
175+ const loadNewer = ( ) => {
176+ const page = paging ( )
177+ if ( ! page ?. hasNewer || page . loading || ! scroll ) return
178+ const bottomDistance = scroll . scrollHeight - scroll . scrollTop - scroll . viewport . height
179+ if ( bottomDistance > LOAD_MORE_THRESHOLD ) return
180+ sync . session . loadNewer ( route . sessionID )
181+ }
182+
135183 const pending = createMemo ( ( ) => {
136184 return messages ( ) . findLast ( ( x ) => x . role === "assistant" && ! x . time . completed ) ?. id
137185 } )
@@ -247,7 +295,7 @@ export function Session() {
247295 const findNextVisibleMessage = ( direction : "next" | "prev" ) : string | null => {
248296 const children = scroll . getChildren ( )
249297 const messagesList = messages ( )
250- const scrollTop = scroll . y
298+ const scrollTop = scroll . scrollTop
251299
252300 // Get visible messages sorted by position, filtering for valid non-synthetic, non-ignored content
253301 const visibleMessages = children
@@ -285,7 +333,7 @@ export function Session() {
285333 }
286334
287335 const child = scroll . getChildren ( ) . find ( ( c ) => c . id === targetID )
288- if ( child ) scroll . scrollBy ( child . y - scroll . y - 1 )
336+ if ( child ) scroll . scrollBy ( child . y - scroll . scrollTop - 1 )
289337 dialog . clear ( )
290338 }
291339
@@ -365,7 +413,7 @@ export function Session() {
365413 const child = scroll . getChildren ( ) . find ( ( child ) => {
366414 return child . id === messageID
367415 } )
368- if ( child ) scroll . scrollBy ( child . y - scroll . y - 1 )
416+ if ( child ) scroll . scrollBy ( child . y - scroll . scrollTop - 1 )
369417 } }
370418 sessionID = { route . sessionID }
371419 setPrompt = { ( promptInfo ) => prompt . set ( promptInfo ) }
@@ -388,7 +436,7 @@ export function Session() {
388436 const child = scroll . getChildren ( ) . find ( ( child ) => {
389437 return child . id === messageID
390438 } )
391- if ( child ) scroll . scrollBy ( child . y - scroll . y - 1 )
439+ if ( child ) scroll . scrollBy ( child . y - scroll . scrollTop - 1 )
392440 } }
393441 sessionID = { route . sessionID }
394442 />
@@ -650,7 +698,16 @@ export function Session() {
650698 category : "Session" ,
651699 hidden : true ,
652700 onSelect : ( dialog ) => {
653- scroll . scrollTo ( 0 )
701+ const page = paging ( )
702+ if ( page ?. hasOlder && ! page . loading ) {
703+ sync . session . jumpToOldest ( route . sessionID ) . then ( ( ) => {
704+ requestAnimationFrame ( ( ) => {
705+ if ( scroll ) scroll . scrollTo ( 0 )
706+ } )
707+ } )
708+ } else {
709+ scroll . scrollTo ( 0 )
710+ }
654711 dialog . clear ( )
655712 } ,
656713 } ,
@@ -661,7 +718,16 @@ export function Session() {
661718 category : "Session" ,
662719 hidden : true ,
663720 onSelect : ( dialog ) => {
664- scroll . scrollTo ( scroll . scrollHeight )
721+ const page = paging ( )
722+ if ( page ?. hasNewer && ! page . loading ) {
723+ sync . session . jumpToLatest ( route . sessionID ) . then ( ( ) => {
724+ requestAnimationFrame ( ( ) => {
725+ if ( scroll ) scroll . scrollTo ( scroll . scrollHeight )
726+ } )
727+ } )
728+ } else {
729+ scroll . scrollTo ( scroll . scrollHeight )
730+ }
665731 dialog . clear ( )
666732 } ,
667733 } ,
@@ -691,7 +757,7 @@ export function Session() {
691757 const child = scroll . getChildren ( ) . find ( ( child ) => {
692758 return child . id === message . id
693759 } )
694- if ( child ) scroll . scrollBy ( child . y - scroll . y - 1 )
760+ if ( child ) scroll . scrollBy ( child . y - scroll . scrollTop - 1 )
695761 break
696762 }
697763 }
@@ -961,8 +1027,38 @@ export function Session() {
9611027 < Show when = { ! sidebarVisible ( ) || ! wide ( ) } >
9621028 < Header />
9631029 </ Show >
1030+ < Show when = { paging ( ) ?. loading && paging ( ) ?. loadingDirection === "older" } >
1031+ < box flexShrink = { 0 } paddingLeft = { 1 } >
1032+ < text fg = { theme . textMuted } > Loading older messages...</ text >
1033+ </ box >
1034+ </ Show >
1035+ < Show when = { ! paging ( ) ?. loading && paging ( ) ?. hasOlder } >
1036+ < box flexShrink = { 0 } paddingLeft = { 1 } >
1037+ < text fg = { theme . textMuted } > (scroll up for more)</ text >
1038+ </ box >
1039+ </ Show >
1040+ < Show when = { paging ( ) ?. error } >
1041+ < box flexShrink = { 0 } paddingLeft = { 1 } >
1042+ < text fg = { theme . error } > Failed to load: { paging ( ) ?. error } </ text >
1043+ < text fg = { theme . textMuted } > (scroll to retry)</ text >
1044+ </ box >
1045+ </ Show >
9641046 < scrollbox
9651047 ref = { ( r ) => ( scroll = r ) }
1048+ onMouseScroll = { ( ) => {
1049+ loadOlder ( )
1050+ loadNewer ( )
1051+ } }
1052+ onKeyDown = { ( e ) => {
1053+ // Standard scroll triggers incremental load
1054+ if ( [ "up" , "pageup" , "home" ] . includes ( e . name ) ) {
1055+ setTimeout ( loadOlder , 0 )
1056+ }
1057+ if ( [ "down" , "pagedown" , "end" ] . includes ( e . name ) ) {
1058+ setTimeout ( loadNewer , 0 )
1059+ }
1060+ } }
1061+ viewportCulling = { true }
9661062 viewportOptions = { {
9671063 paddingRight : showScrollbar ( ) ? 1 : 0 ,
9681064 } }
@@ -1075,6 +1171,16 @@ export function Session() {
10751171 ) }
10761172 </ For >
10771173 </ scrollbox >
1174+ < Show when = { paging ( ) ?. loading && paging ( ) ?. loadingDirection === "newer" } >
1175+ < box flexShrink = { 0 } paddingLeft = { 1 } >
1176+ < text fg = { theme . textMuted } > Loading newer messages...</ text >
1177+ </ box >
1178+ </ Show >
1179+ < Show when = { ! paging ( ) ?. loading && paging ( ) ?. hasNewer } >
1180+ < box flexShrink = { 0 } paddingLeft = { 1 } >
1181+ < text fg = { theme . textMuted } > (scroll down for more)</ text >
1182+ </ box >
1183+ </ Show >
10781184 < box flexShrink = { 0 } >
10791185 < Show when = { permissions ( ) . length > 0 } >
10801186 < PermissionPrompt request = { permissions ( ) [ 0 ] } />
0 commit comments