File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 88 ChatMessages ,
99 ChatScreenDragOverlay ,
1010 ChatScreenProcessingInfo ,
11+ ChatScreenActionScrollDown ,
1112 DialogEmptyFileAlert ,
1213 DialogFileUploadError ,
1314 DialogChatError ,
338339 });
339340
340341 function handleMessagesReady() {
341- if (! disableAutoScroll && ! autoScroll .userScrolledUp ) {
342+ if (disableAutoScroll ) return ;
343+
344+ if (! autoScroll .userScrolledUp ) {
342345 requestAnimationFrame (() => {
343346 autoScroll .scrollToBottom (' instant' );
344347 });
405408 <div
406409 class ="pointer-events-none {isEmpty
407410 ? ' absolute bottom-[calc(50dvh-7rem)]'
408- : ' sticky bottom-4' } right-4 left-4 mt-auto pt-16 transition-all duration-200"
411+ : ' sticky bottom-4' } right-4 left-4 mt-auto -mb-14 pt-16 transition-all duration-200"
409412 >
410413 {#if isEmpty }
411414 <div class ="mb-8 px-4 text-center" use:fadeInView ={{ duration : 300 }}>
419422 </div >
420423 {/if }
421424
425+ <ChatScreenActionScrollDown container ={chatScrollContainer } />
426+
422427 {#if page .params .id }
423428 <ChatScreenProcessingInfo />
424429 {/if }
Original file line number Diff line number Diff line change 1+ <script lang =" ts" >
2+ import { ArrowDown } from ' @lucide/svelte' ;
3+ import { Button } from ' $lib/components/ui/button' ;
4+
5+ let { container }: { container: HTMLDivElement | undefined } = $props ();
6+
7+ let show = $state (false );
8+
9+ function checkVisibility() {
10+ if (! container ) return ;
11+ const { scrollTop, scrollHeight, clientHeight } = container ;
12+ const distanceFromBottom = scrollHeight - clientHeight - scrollTop ;
13+ show = distanceFromBottom > clientHeight * 0.5 ;
14+ }
15+
16+ function scrollToBottom() {
17+ if (container ) {
18+ container .scrollTo ({
19+ top: container .scrollHeight ,
20+ behavior: ' smooth'
21+ });
22+ }
23+ }
24+
25+ $effect (() => {
26+ const c = container ;
27+ if (c ) {
28+ c .addEventListener (' scroll' , checkVisibility );
29+ checkVisibility ();
30+ return () => {
31+ c .removeEventListener (' scroll' , checkVisibility );
32+ };
33+ }
34+ });
35+ </script >
36+
37+ <div class =" pointer-events-auto relative z-50 mx-auto mb-4 flex max-w-[48rem] justify-center" >
38+ <Button
39+ onclick ={scrollToBottom }
40+ variant =" secondary"
41+ size =" icon"
42+ class =" h-10 w-10 rounded-full bg-background/80 shadow-lg backdrop-blur-sm transition-all duration-200 hover:bg-muted/80"
43+ aria-label =" Scroll to bottom"
44+ style ="transform: translateY( {show ? ' 0' : ' 20px' }); opacity: {show ? 1 : 0 };"
45+ >
46+ <ArrowDown class =" h-4 w-4" />
47+ </Button >
48+ </div >
Original file line number Diff line number Diff line change @@ -667,3 +667,10 @@ export { default as ChatScreenForm } from './ChatScreen/ChatScreenForm.svelte';
667667 * Only visible when `isCurrentConversationLoading` is true.
668668 */
669669export { default as ChatScreenProcessingInfo } from './ChatScreen/ChatScreenProcessingInfo.svelte' ;
670+
671+ /**
672+ * Scroll-to-bottom action button. Displays a floating button when the user
673+ * has scrolled up more than half a viewport height from the bottom.
674+ * Takes the chat container element as a prop to manage scroll state internally.
675+ */
676+ export { default as ChatScreenActionScrollDown } from './ChatScreen/ChatScreenActionScrollDown.svelte' ;
Original file line number Diff line number Diff line change @@ -100,6 +100,14 @@ export class AutoScrollController {
100100 this . _autoScrollEnabled = true ;
101101 }
102102
103+ /**
104+ * Resets scroll state when switching conversations.
105+ */
106+ resetScrollState ( ) : void {
107+ this . _userScrolledUp = false ;
108+ this . _autoScrollEnabled = true ;
109+ }
110+
103111 /**
104112 * Starts the auto-scroll interval for continuous scrolling during streaming.
105113 */
You can’t perform that action at this time.
0 commit comments