diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz index 439e0e4f8f..1af79f6b22 100644 Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ diff --git a/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte b/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte index 6c75ba670b..4e9fc63dc8 100644 --- a/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte +++ b/tools/server/webui/src/lib/components/app/notebook/NotebookScreen.svelte @@ -16,6 +16,21 @@ let inputContent = $state(content); + import { + AUTO_SCROLL_AT_BOTTOM_THRESHOLD, + AUTO_SCROLL_INTERVAL, + INITIAL_SCROLL_DELAY + } from '$lib/constants/auto-scroll'; + import { onMount } from 'svelte'; + + let disableAutoScroll = $derived(Boolean(config().disableAutoScroll)); + let autoScrollEnabled = $state(true); + let scrollContainer: HTMLTextAreaElement | null = $state(null); + let lastScrollTop = $state(0); + let scrollInterval: ReturnType | undefined; + let scrollTimeout: ReturnType | undefined; + let userScrolledUp = $state(false); + let isRouter = $derived(isRouterMode()); // Sync local input with store content @@ -29,6 +44,12 @@ } async function handleGenerate() { + if (!disableAutoScroll) { + userScrolledUp = false; + autoScrollEnabled = true; + scrollToBottom(); + } + if (notebookModel == null) { notebookModel = activeModelId; } @@ -93,6 +114,68 @@ notebookModel = modelName; } }); + + function handleScroll() { + if (disableAutoScroll || !scrollContainer) return; + + const { scrollTop, scrollHeight, clientHeight } = scrollContainer; + const distanceFromBottom = scrollHeight - scrollTop - clientHeight; + const isAtBottom = distanceFromBottom < AUTO_SCROLL_AT_BOTTOM_THRESHOLD; + + if (scrollTop < lastScrollTop && !isAtBottom) { + userScrolledUp = true; + autoScrollEnabled = false; + } else if (isAtBottom && userScrolledUp) { + userScrolledUp = false; + autoScrollEnabled = true; + } + + if (scrollTimeout) { + clearTimeout(scrollTimeout); + } + + scrollTimeout = setTimeout(() => { + if (isAtBottom) { + userScrolledUp = false; + autoScrollEnabled = true; + } + }, AUTO_SCROLL_INTERVAL); + + lastScrollTop = scrollTop; + } + + function scrollToBottom(behavior: ScrollBehavior = 'smooth') { + if (disableAutoScroll) return; + + scrollContainer?.scrollTo({ + top: scrollContainer?.scrollHeight, + behavior + }); + } + + onMount(() => { + if (!disableAutoScroll) { + setTimeout(() => scrollToBottom('instant'), INITIAL_SCROLL_DELAY); + } + }); + + $effect(() => { + if (disableAutoScroll) { + autoScrollEnabled = false; + if (scrollInterval) { + clearInterval(scrollInterval); + scrollInterval = undefined; + } + return; + } + + if (notebookStore.isGenerating && autoScrollEnabled) { + scrollInterval = setInterval(() => scrollToBottom(), AUTO_SCROLL_INTERVAL); + } else if (scrollInterval) { + clearInterval(scrollInterval); + scrollInterval = undefined; + } + });
@@ -109,6 +192,8 @@