Skip to content

Commit adf99a3

Browse files
allozaurrsenthilkumar6
authored andcommitted
feat: add scroll-to-bottom button to chat + prevent forced scroll down (ggml-org#23270)
1 parent 2a48106 commit adf99a3

4 files changed

Lines changed: 70 additions & 2 deletions

File tree

tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ChatMessages,
99
ChatScreenDragOverlay,
1010
ChatScreenProcessingInfo,
11+
ChatScreenActionScrollDown,
1112
DialogEmptyFileAlert,
1213
DialogFileUploadError,
1314
DialogChatError,
@@ -338,7 +339,9 @@
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
});
@@ -405,7 +408,7 @@
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 }}>
@@ -419,6 +422,8 @@
419422
</div>
420423
{/if}
421424

425+
<ChatScreenActionScrollDown container={chatScrollContainer} />
426+
422427
{#if page.params.id}
423428
<ChatScreenProcessingInfo />
424429
{/if}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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>

tools/ui/src/lib/components/app/chat/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,3 +667,10 @@ export { default as ChatScreenForm } from './ChatScreen/ChatScreenForm.svelte';
667667
* Only visible when `isCurrentConversationLoading` is true.
668668
*/
669669
export { 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';

tools/ui/src/lib/hooks/use-auto-scroll.svelte.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff 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
*/

0 commit comments

Comments
 (0)