Skip to content

Commit 6af3542

Browse files
committed
feat: Structural improvements + auto-scroll fixes
1 parent f45f9c8 commit 6af3542

File tree

13 files changed

+78
-137
lines changed

13 files changed

+78
-137
lines changed

tools/server/public/index.html.gz

-125 Bytes
Binary file not shown.

tools/server/webui/src/lib/components/chat/ChatHeader.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { Button } from '$lib/components/ui/button';
33
import { Settings } from '@lucide/svelte';
4-
import ChatSettingsDialog from './ChatSettingsDialog.svelte';
4+
import { ChatSettingsDialog } from '$lib/components';
55
66
let settingsOpen = $state(false);
77

tools/server/webui/src/lib/components/chat/ChatMessage.svelte

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import { Edit, Copy, RefreshCw, Check, X } from '@lucide/svelte';
66
import type { ChatRole } from '$lib/types/chat';
77
import type { Message } from '$lib/types/database';
8-
import ThinkingSection from './ThinkingSection.svelte';
9-
import MarkdownContent from './MarkdownContent.svelte';
8+
import { ChatThinkingBlock, MarkdownContent } from '$lib/components';
109
import { parseThinkingContent } from '$lib/utils/thinking';
1110
import { copyToClipboard } from '$lib/utils/copy';
1211
@@ -206,7 +205,7 @@
206205
aria-label="Assistant message with actions"
207206
>
208207
{#if thinkingContent}
209-
<ThinkingSection thinking={thinkingContent} isStreaming={!message.timestamp} />
208+
<ChatThinkingBlock thinking={thinkingContent} isStreaming={!message.timestamp} />
210209
{/if}
211210

212211
{#if message.role === 'assistant'}
Lines changed: 14 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,27 @@
11
<script lang="ts">
22
import type { Message } from '$lib/types/database';
33
import { updateMessage, regenerateMessage } from '$lib/stores/chat.svelte';
4-
import ChatMessage from './ChatMessage.svelte';
4+
import { ChatMessage } from '$lib/components';
55
interface Props {
66
class?: string;
77
messages?: Message[];
88
isLoading?: boolean;
99
}
1010
1111
let { class: className, messages = [], isLoading = false }: Props = $props();
12-
13-
let scrollContainer = $state<HTMLDivElement>();
14-
let shouldAutoScroll = $state(true);
15-
let lastScrollHeight = $state(0);
16-
17-
// Check if user is at the bottom of the scroll container
18-
function isAtBottom(): boolean {
19-
if (!scrollContainer) return false;
20-
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
21-
// Consider "at bottom" if within 100px of the bottom
22-
return scrollHeight - scrollTop - clientHeight < 100;
23-
}
24-
25-
// Handle scroll events to detect manual scrolling
26-
function handleScroll() {
27-
if (!scrollContainer) return;
28-
29-
// If user scrolled to bottom, re-enable auto-scroll
30-
if (isAtBottom()) {
31-
shouldAutoScroll = true;
32-
} else {
33-
// If user scrolled up manually, disable auto-scroll
34-
shouldAutoScroll = false;
35-
}
36-
}
37-
38-
// Auto-scroll when messages change or loading state changes
39-
$effect(() => {
40-
if (scrollContainer && shouldAutoScroll) {
41-
const currentScrollHeight = scrollContainer.scrollHeight;
42-
43-
// Only scroll if content has actually changed (new messages or loading)
44-
if (currentScrollHeight !== lastScrollHeight) {
45-
setTimeout(() => {
46-
if (scrollContainer && shouldAutoScroll) {
47-
scrollContainer.scrollTop = scrollContainer.scrollHeight;
48-
lastScrollHeight = scrollContainer.scrollHeight;
49-
}
50-
}, 0);
51-
}
52-
}
53-
});
54-
55-
// Update lastScrollHeight when messages change
56-
$effect(() => {
57-
if (scrollContainer) {
58-
lastScrollHeight = scrollContainer.scrollHeight;
59-
}
60-
});
6112
</script>
6213

63-
<div class="flex h-full flex-col {className}">
64-
<div
65-
bind:this={scrollContainer}
66-
class="bg-background flex-1 overflow-y-auto p-4"
67-
onscroll={handleScroll}
68-
>
69-
<div class="mb-48 mt-16 space-y-6">
70-
{#each messages as message}
71-
<ChatMessage
72-
class="mx-auto w-full max-w-[56rem]"
73-
{message}
74-
onUpdateMessage={async (msg, newContent) => {
75-
await updateMessage(msg.id, newContent);
76-
}}
77-
onRegenerate={async (msg) => {
78-
await regenerateMessage(msg.id);
79-
}}
80-
/>
81-
{/each}
82-
</div>
83-
</div>
14+
<div class="flex h-full flex-col space-y-10 pt-16 md:pt-24 {className}" style="height: auto;">
15+
{#each messages as message}
16+
<ChatMessage
17+
class="mx-auto w-full max-w-[56rem]"
18+
{message}
19+
onUpdateMessage={async (msg, newContent) => {
20+
await updateMessage(msg.id, newContent);
21+
}}
22+
onRegenerate={async (msg) => {
23+
await regenerateMessage(msg.id);
24+
}}
25+
/>
26+
{/each}
8427
</div>

tools/server/webui/src/lib/components/chat/ChatScreen.svelte

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
<script lang="ts">
2-
import ChatMessages from './ChatMessages.svelte';
3-
import ChatForm from './ChatForm.svelte';
4-
import ServerInfo from './ServerInfo.svelte';
2+
import { afterNavigate } from '$app/navigation';
3+
import { navigating } from '$app/state';
4+
import { ChatForm, ChatMessages, ServerInfo } from '$lib/components';
55
import {
66
activeMessages,
77
activeConversation,
88
isLoading,
99
sendMessage,
10-
stopGeneration
10+
stopGeneration,
11+
12+
chatStore
13+
1114
} from '$lib/stores/chat.svelte';
15+
import { onMount } from 'svelte';
1216
import { fly, slide } from 'svelte/transition';
1317
1418
let { showCenteredEmpty = false } = $props();
@@ -20,18 +24,63 @@
2024
async function handleSendMessage(message: string) {
2125
await sendMessage(message);
2226
}
27+
28+
let chatScrollContainer: HTMLDivElement | undefined = $state();
29+
30+
function scrollChatToBottom() {
31+
chatScrollContainer?.scrollTo({top: chatScrollContainer?.scrollHeight, behavior: 'instant'})
32+
}
33+
34+
afterNavigate(() => {
35+
setTimeout(scrollChatToBottom, 10); // This is a dirty workaround, need to find racing conditions
36+
})
37+
38+
onMount(() => {
39+
setTimeout(scrollChatToBottom, 10); // This is a dirty workaround, need to find racing conditions
40+
})
41+
42+
let scrollInterval: ReturnType<typeof setInterval> | undefined;
43+
let autoScrollEnabled = $state(true);
44+
45+
function handleScroll() {
46+
if (!chatScrollContainer) return;
47+
48+
const { scrollTop, scrollHeight, clientHeight } = chatScrollContainer;
49+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
50+
51+
// Disable auto-scroll when user scrolls up 50px+ from bottom
52+
if (distanceFromBottom > 50) {
53+
autoScrollEnabled = false;
54+
}
55+
// Re-enable auto-scroll when user reaches bottom
56+
else if (distanceFromBottom <= 1) {
57+
autoScrollEnabled = true;
58+
}
59+
}
60+
61+
$effect(() => {
62+
if (isLoading() && autoScrollEnabled) {
63+
scrollInterval = setInterval(scrollChatToBottom, 100);
64+
} else {
65+
if (scrollInterval) {
66+
clearInterval(scrollInterval);
67+
scrollInterval = undefined;
68+
}
69+
}
70+
})
71+
72+
$inspect(chatStore.isLoading)
2373
</script>
2474

2575
{#if !isEmpty}
26-
<div class="flex h-full flex-col">
27-
<div class="flex-1 overflow-hidden">
76+
<div class="flex h-full flex-col overflow-y-auto" bind:this={chatScrollContainer} onscroll={handleScroll}>
2877
<ChatMessages class="mb-36" messages={activeMessages()} isLoading={isLoading()} />
2978

3079
<div
3180
class="z-999 sticky bottom-0 m-auto max-w-[56rem]"
3281
in:slide={{ duration: 400, axis: 'y' }}
3382
>
34-
<div class="bg-background m-auto rounded-t-3xl border-t pb-4">
83+
<div class="bg-background m-auto rounded-t-3xl border-t pb-4 min-w-[56rem]">
3584
<ChatForm
3685
isLoading={isLoading()}
3786
showHelperText={false}
@@ -40,7 +89,6 @@
4089
/>
4190
</div>
4291
</div>
43-
</div>
4492
</div>
4593
{:else}
4694
<div class="flex h-full items-center justify-center">

tools/server/webui/src/lib/components/chat/ChatSidebar/ChatSidebarFooter.svelte

Lines changed: 0 additions & 13 deletions
This file was deleted.

tools/server/webui/src/lib/components/chat/ChatSidebar/ChatSidebarHeader.svelte

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)