Skip to content

Commit c4a0f57

Browse files
committed
smarter scroll
1 parent 80e043e commit c4a0f57

File tree

2 files changed

+54
-24
lines changed

2 files changed

+54
-24
lines changed

src/MaIN.InferPage/Components/Pages/Home.razor

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
{
8383
<div class="reasoning-row">
8484
<span class="brain-toggle" title="@(conversation.ShowReason ? "Hide reasoning" : "Show reasoning")"
85-
@onclick="@(() => { _preserveScroll = true; conversation.ShowReason = !conversation.ShowReason; })">
85+
@onclick="@(() => ToggleReasoning(conversation))">
8686
<FluentIcon Value="@(new Icons.Regular.Size24.Brain())"
8787
Style="@($"fill: {(conversation.ShowReason ? "var(--accent-base-color)" : "var(--neutral-foreground-hint)")};")"/>
8888
</span>
@@ -213,15 +213,14 @@
213213
{
214214
var theme = await JS.InvokeAsync<string>("themeManager.load");
215215
_accentColor = (theme == "dark" || theme == "system-dark") ? "#00ffcc" : "#00cca3";
216-
StateHasChanged();
217-
}
218-
else if (!_preserveScroll)
219-
{
216+
await JS.InvokeVoidAsync("scrollManager.attachScrollListener", "messages-container");
220217
await JS.InvokeVoidAsync("scrollManager.restoreScrollPosition", "messages-container");
218+
StateHasChanged();
221219
}
222-
else
220+
else if (_preserveScroll)
223221
{
224222
_preserveScroll = false;
223+
await JS.InvokeVoidAsync("scrollManager.restoreScrollPosition", "messages-container");
225224
}
226225
}
227226

@@ -346,9 +345,12 @@
346345
Chat.ModelId = Utils.Model!;
347346
Chat.Visual = Utils.Visual;
348347

348+
bool wasAtBottom = await JS.InvokeAsync<bool>("scrollManager.isAtBottom", "messages-container");
349+
349350
StateHasChanged();
350351

351-
bool wasAtBottom = await JS.InvokeAsync<bool>("scrollManager.isAtBottom", "messages-container");
352+
if (wasAtBottom)
353+
await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", "messages-container");
352354

353355
var request = ctx!.WithMessage(msg);
354356
if (attachments.Count != 0)
@@ -379,7 +381,7 @@
379381
await InvokeAsync(StateHasChanged);
380382
if (wasAtBottom)
381383
{
382-
await InvokeAsync(async () => await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", cancellationToken, _bottomElement));
384+
await InvokeAsync(async () => await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", "messages-container"));
383385
}
384386
}, cancellationToken: cancellationToken);
385387

@@ -388,10 +390,11 @@
388390
var currentChat = await ctx.GetCurrentChat();
389391
Chat.Messages.Add(currentChat.Messages.Last());
390392

393+
await JS.InvokeVoidAsync("scrollManager.saveScrollPosition", "messages-container");
394+
_preserveScroll = true;
391395
RebuildMessagesWithFiles();
392396
_incomingReasoning = null;
393397
_incomingMessage = null;
394-
await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement);
395398
}
396399
catch (OperationCanceledException)
397400
{
@@ -475,6 +478,13 @@
475478
: MessageType.CloudLLM;
476479
}
477480

481+
private async Task ToggleReasoning(MessageExt conversation)
482+
{
483+
await JS.InvokeVoidAsync("scrollManager.saveScrollPosition", "messages-container");
484+
_preserveScroll = true;
485+
conversation.ShowReason = !conversation.ShowReason;
486+
}
487+
478488
private void RebuildMessagesWithFiles()
479489
{
480490
var existingFilesMap = Messages
@@ -485,7 +495,7 @@
485495
{
486496
Message = x,
487497
AttachedFiles = existingFilesMap.TryGetValue(x, out var files) ? files : new List<string>(),
488-
ShowReason = x.Tokens.Any(t => t.Type == TokenType.Reason)
498+
ShowReason = false
489499
}).ToList();
490500
}
491501

src/MaIN.InferPage/wwwroot/scroll.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
window.scrollManager = {
2-
isUserScrolling: false,
2+
userScrolledUp: false,
3+
isProgrammaticScroll: false,
4+
_savedScrollTop: null,
35

46
saveScrollPosition: (containerId) => {
57
const container = document.getElementById(containerId);
68
if (!container) return;
7-
sessionStorage.setItem("scrollTop", container.scrollTop);
9+
window.scrollManager._savedScrollTop = container.scrollTop;
10+
container.style.overflowY = 'hidden';
811
},
912

1013
restoreScrollPosition: (containerId) => {
1114
const container = document.getElementById(containerId);
1215
if (!container) return;
13-
container.scrollTop = 9999;
16+
if (window.scrollManager._savedScrollTop !== null) {
17+
container.scrollTop = window.scrollManager._savedScrollTop;
18+
window.scrollManager._savedScrollTop = null;
19+
} else {
20+
container.scrollTop = container.scrollHeight;
21+
}
22+
container.style.overflowY = '';
1423
},
1524

1625
isAtBottom: (containerId) => {
@@ -19,24 +28,35 @@ window.scrollManager = {
1928
return container.scrollHeight - container.scrollTop <= container.clientHeight + 50;
2029
},
2130

22-
scrollToBottomSmooth: (bottomElement) => {
23-
if (!bottomElement) return;
24-
if (!window.scrollManager.isUserScrolling) {
25-
bottomElement.scrollIntoView({ behavior: 'smooth' });
26-
}
31+
scrollToBottomSmooth: (containerId) => {
32+
if (window.scrollManager.userScrolledUp) return;
33+
const container = document.getElementById(containerId);
34+
if (!container) return;
35+
window.scrollManager.isProgrammaticScroll = true;
36+
container.scrollTop = container.scrollHeight;
37+
window.scrollManager.isProgrammaticScroll = false;
2738
},
2839

2940
attachScrollListener: (containerId) => {
3041
const container = document.getElementById(containerId);
3142
if (!container) return;
3243

44+
container.addEventListener("wheel", (e) => {
45+
if (e.deltaY < 0) {
46+
window.scrollManager.userScrolledUp = true;
47+
}
48+
});
49+
50+
container.addEventListener("touchmove", () => {
51+
window.scrollManager.userScrolledUp = true;
52+
});
53+
3354
container.addEventListener("scroll", () => {
34-
window.scrollManager.isUserScrolling =
35-
container.scrollHeight - container.scrollTop > container.clientHeight + 50;
55+
if (window.scrollManager.isProgrammaticScroll) return;
56+
const atBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 50;
57+
if (atBottom) {
58+
window.scrollManager.userScrolledUp = false;
59+
}
3660
});
3761
}
3862
};
39-
40-
document.addEventListener("DOMContentLoaded", () => {
41-
window.scrollManager.attachScrollListener("bottom");
42-
});

0 commit comments

Comments
 (0)