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}
--- /dev/null
+<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>
* 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';
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.
*/