char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1import { AUTO_SCROLL_AT_BOTTOM_THRESHOLD , AUTO_SCROLL_INTERVAL } from '$lib/constants' ; 2 3export interface AutoScrollOptions { 4disabled ?:boolean ; 5} 6 7/** 8* Creates an auto-scroll controller for a scrollable container. 9* 10* Features: 11* - Auto-scrolls to bottom during streaming/loading 12* - Stops auto-scroll when user manually scrolls up 13* - Resumes auto-scroll when user scrolls back to bottom 14*/ 15export class AutoScrollController { 16private _autoScrollEnabled = $state ( true ); 17private _container :HTMLElement | undefined ; 18private _disabled :boolean ; 19private _lastScrollTop = $state ( 0 ); 20private _mutationObserver :MutationObserver | null = null ; 21private _observerEnabled = false ; 22private _rafPending = false ; 23private _scrollInterval :ReturnType < typeof setInterval > | undefined ; 24private _userScrolledUp = $state ( false ); 25get autoScrollEnabled () :boolean { 26return this . _autoScrollEnabled ; 27} 28 29get userScrolledUp () :boolean { 30return this . _userScrolledUp ; 31} 32 33constructor ( options :AutoScrollOptions = {}) { 34this . _disabled = options . disabled ?? false ; 35} 36 37/** 38* Cleans up resources. Call this in onDestroy or when the component unmounts. 39*/ 40destroy () :void { 41this . stopInterval (); 42this . _doStopObserving (); 43} 44 45/** 46* Enables auto-scroll (e.g., when user sends a message). 47*/ 48enable () :void { 49if ( this . _disabled ) return ; 50 51this . _userScrolledUp = false ; 52this . _autoScrollEnabled = true ; 53} 54 55/** 56* Handles scroll events to detect user scroll direction and toggle auto-scroll. 57*/ 58handleScroll () :void { 59if ( this . _disabled || ! this . _container ) return ; 60 61const { clientHeight, scrollHeight, scrollTop} = this . _container ; 62const distanceFromBottom = scrollHeight - clientHeight - scrollTop ; 63const isScrollingUp = scrollTop < this . _lastScrollTop ; 64const isAtBottom = distanceFromBottom < AUTO_SCROLL_AT_BOTTOM_THRESHOLD ; 65 66if ( isScrollingUp && ! isAtBottom ) { 67this . _userScrolledUp = true ; 68this . _autoScrollEnabled = false ; 69} else if ( isAtBottom && this . _userScrolledUp ) { 70this . _userScrolledUp = false ; 71this . _autoScrollEnabled = true ; 72} 73 74this . _lastScrollTop = scrollTop ; 75} 76 77/** 78* Resets scroll state when switching conversations. 79*/ 80resetScrollState () :void { 81this . _userScrolledUp = false ; 82this . _autoScrollEnabled = ! this . _disabled ; 83 84if ( this . _container ) { 85this . _lastScrollTop = this . _container . scrollTop ; 86} 87} 88 89/** 90* Scrolls the container to the bottom instantly. 91*/ 92scrollToBottom () :void { 93if ( this . _disabled || ! this . _container ) return ; 94 95this . _container . scrollTop = this . _container . scrollHeight ; 96} 97 98/** 99* Binds the controller to a scrollable container element. 100*/ 101setContainer ( container :HTMLElement | undefined ) :void { 102this . _doStopObserving (); 103this . _container = container ; 104 105if ( this . _observerEnabled && container && ! this . _disabled ) { 106this . _doStartObserving (); 107} 108} 109 110/** 111* Updates the disabled state. 112*/ 113setDisabled ( disabled :boolean ) :void { 114if ( this . _disabled === disabled ) return ; 115 116this . _disabled = disabled ; 117 118if ( disabled ) { 119this . _autoScrollEnabled = false ; 120this . stopInterval (); 121this . _doStopObserving (); 122} else if ( this . _observerEnabled && this . _container && ! this . _mutationObserver ) { 123this . _doStartObserving (); 124} 125} 126 127/** 128* Starts the auto-scroll interval for continuous scrolling during streaming. 129*/ 130startInterval () :void { 131if ( this . _disabled || this . _scrollInterval ) return ; 132 133this . _scrollInterval = setInterval (() => { 134this . scrollToBottom (); 135}, AUTO_SCROLL_INTERVAL ); 136} 137 138/** 139* Starts a MutationObserver on the container that auto-scrolls to bottom 140* on content changes. More responsive than interval-based polling. 141*/ 142startObserving () :void { 143this . _observerEnabled = true ; 144 145if ( this . _container && ! this . _disabled && ! this . _mutationObserver ) { 146this . _doStartObserving (); 147} 148} 149 150/** 151* Stops the auto-scroll interval. 152*/ 153stopInterval () :void { 154if ( this . _scrollInterval ) { 155clearInterval ( this . _scrollInterval ); 156this . _scrollInterval = undefined ; 157} 158} 159 160/** 161* Stops the MutationObserver. 162*/ 163stopObserving () :void { 164this . _observerEnabled = false ; 165this . _doStopObserving (); 166} 167 168/** 169* Updates the auto-scroll interval based on streaming state. 170* Call this in a $effect to automatically manage the interval. 171*/ 172updateInterval ( isStreaming :boolean ) :void { 173if ( this . _disabled ) { 174this . stopInterval (); 175 176return ; 177} 178 179if ( isStreaming && this . _autoScrollEnabled ) { 180if ( ! this . _scrollInterval ) { 181this . startInterval (); 182} 183} else { 184this . stopInterval (); 185} 186} 187 188private _doStartObserving () :void { 189if ( ! this . _container || this . _mutationObserver ) return ; 190 191this . _mutationObserver = new MutationObserver (() => { 192if ( ! this . _autoScrollEnabled || this . _rafPending ) return ; 193 194this . _rafPending = true ; 195requestAnimationFrame (() => { 196this . _rafPending = false ; 197 198if ( this . _autoScrollEnabled && this . _container ) { 199this . _container . scrollTop = this . _container . scrollHeight ; 200} 201}); 202}); 203 204this . _mutationObserver . observe ( this . _container , { 205characterData :true , 206childList :true , 207subtree :true 208}); 209} 210 211private _doStopObserving () :void { 212if ( this . _mutationObserver ) { 213this . _mutationObserver . disconnect (); 214this . _mutationObserver = null ; 215} 216 217this . _rafPending = false ; 218} 219} 220 221/** 222* Creates a new AutoScrollController instance. 223*/ 224export function createAutoScrollController ( options :AutoScrollOptions = {}) :AutoScrollController { 225return new AutoScrollController ( options ); 226}