]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
feat: add scroll-to-bottom button to chat + prevent forced scroll down (#23270)
authorAleksander Grygier <redacted>
Mon, 18 May 2026 14:17:21 +0000 (16:17 +0200)
committerGitHub <redacted>
Mon, 18 May 2026 14:17:21 +0000 (16:17 +0200)
tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte [new file with mode: 0644]
tools/ui/src/lib/components/app/chat/index.ts
tools/ui/src/lib/hooks/use-auto-scroll.svelte.ts

index dc3eab134f40be17333c955db5ca4826ac00479a..bd93a569ce62dc3359f146b3c516935b716c7c7a 100644 (file)
@@ -8,6 +8,7 @@
                ChatMessages,
                ChatScreenDragOverlay,
                ChatScreenProcessingInfo,
+               ChatScreenActionScrollDown,
                DialogEmptyFileAlert,
                DialogFileUploadError,
                DialogChatError,
        });
 
        function handleMessagesReady() {
-               if (!disableAutoScroll && !autoScroll.userScrolledUp) {
+               if (disableAutoScroll) return;
+
+               if (!autoScroll.userScrolledUp) {
                        requestAnimationFrame(() => {
                                autoScroll.scrollToBottom('instant');
                        });
                        <div
                                class="pointer-events-none {isEmpty
                                        ? 'absolute bottom-[calc(50dvh-7rem)]'
-                                       : 'sticky bottom-4'} right-4 left-4 mt-auto pt-16 transition-all duration-200"
+                                       : 'sticky bottom-4'} right-4 left-4 mt-auto -mb-14 pt-16 transition-all duration-200"
                        >
                                {#if isEmpty}
                                        <div class="mb-8 px-4 text-center" use:fadeInView={{ duration: 300 }}>
                                        </div>
                                {/if}
 
+                               <ChatScreenActionScrollDown container={chatScrollContainer} />
+
                                {#if page.params.id}
                                        <ChatScreenProcessingInfo />
                                {/if}
diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte
new file mode 100644 (file)
index 0000000..3f3ee86
--- /dev/null
@@ -0,0 +1,48 @@
+<script lang="ts">
+       import { ArrowDown } from '@lucide/svelte';
+       import { Button } from '$lib/components/ui/button';
+
+       let { container }: { container: HTMLDivElement | undefined } = $props();
+
+       let show = $state(false);
+
+       function checkVisibility() {
+               if (!container) return;
+               const { scrollTop, scrollHeight, clientHeight } = container;
+               const distanceFromBottom = scrollHeight - clientHeight - scrollTop;
+               show = distanceFromBottom > clientHeight * 0.5;
+       }
+
+       function scrollToBottom() {
+               if (container) {
+                       container.scrollTo({
+                               top: container.scrollHeight,
+                               behavior: 'smooth'
+                       });
+               }
+       }
+
+       $effect(() => {
+               const c = container;
+               if (c) {
+                       c.addEventListener('scroll', checkVisibility);
+                       checkVisibility();
+                       return () => {
+                               c.removeEventListener('scroll', checkVisibility);
+                       };
+               }
+       });
+</script>
+
+<div class="pointer-events-auto relative z-50 mx-auto mb-4 flex max-w-[48rem] justify-center">
+       <Button
+               onclick={scrollToBottom}
+               variant="secondary"
+               size="icon"
+               class="h-10 w-10 rounded-full bg-background/80 shadow-lg backdrop-blur-sm transition-all duration-200 hover:bg-muted/80"
+               aria-label="Scroll to bottom"
+               style="transform: translateY({show ? '0' : '20px'}); opacity: {show ? 1 : 0};"
+       >
+               <ArrowDown class="h-4 w-4" />
+       </Button>
+</div>
index 5f65979803cc3755f7df21f86c4e225e4e76bf28..9c7ce864e216ec9836d3b6d20eb71d0cd2fa5b84 100644 (file)
@@ -667,3 +667,10 @@ export { default as ChatScreenForm } from './ChatScreen/ChatScreenForm.svelte';
  * Only visible when `isCurrentConversationLoading` is true.
  */
 export { default as ChatScreenProcessingInfo } from './ChatScreen/ChatScreenProcessingInfo.svelte';
+
+/**
+ * Scroll-to-bottom action button. Displays a floating button when the user
+ * has scrolled up more than half a viewport height from the bottom.
+ * Takes the chat container element as a prop to manage scroll state internally.
+ */
+export { default as ChatScreenActionScrollDown } from './ChatScreen/ChatScreenActionScrollDown.svelte';
index f59e3ed4b72081d9e09bd51a72539c54b623d72b..7bac452e4e9dbf2dc3c07587beadd4ac7fc38958 100644 (file)
@@ -100,6 +100,14 @@ export class AutoScrollController {
                this._autoScrollEnabled = true;
        }
 
+       /**
+        * Resets scroll state when switching conversations.
+        */
+       resetScrollState(): void {
+               this._userScrolledUp = false;
+               this._autoScrollEnabled = true;
+       }
+
        /**
         * Starts the auto-scroll interval for continuous scrolling during streaming.
         */