From 1b3fff67ecd467eb7348dd68c659b16f35c07d4c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 23 Aug 2024 10:32:27 +0200 Subject: [PATCH] Improved chat page by scrolling to the bottom after loading (#87) --- .../Components/InnerScrolling.razor | 4 ++++ .../Components/InnerScrolling.razor.cs | 10 ++++++++++ app/MindWork AI Studio/Pages/Chat.razor | 2 +- app/MindWork AI Studio/Pages/Chat.razor.cs | 19 +++++++++++++++++++ app/MindWork AI Studio/Pages/Settings.razor | 1 + .../Settings/DataModel/DataChat.cs | 5 +++++ .../Tools/ElementReferenceExtensions.cs | 11 +++++++++++ app/MindWork AI Studio/wwwroot/app.js | 4 ++++ .../wwwroot/changelog/v0.8.12.md | 3 ++- 9 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/ElementReferenceExtensions.cs diff --git a/app/MindWork AI Studio/Components/InnerScrolling.razor b/app/MindWork AI Studio/Components/InnerScrolling.razor index f0c66d6..bad2c09 100644 --- a/app/MindWork AI Studio/Components/InnerScrolling.razor +++ b/app/MindWork AI Studio/Components/InnerScrolling.razor @@ -3,6 +3,10 @@
@this.ChildContent + +
+   +
@this.FooterContent diff --git a/app/MindWork AI Studio/Components/InnerScrolling.razor.cs b/app/MindWork AI Studio/Components/InnerScrolling.razor.cs index b204f4e..42d75a0 100644 --- a/app/MindWork AI Studio/Components/InnerScrolling.razor.cs +++ b/app/MindWork AI Studio/Components/InnerScrolling.razor.cs @@ -26,6 +26,11 @@ public partial class InnerScrolling : MSGComponentBase [CascadingParameter] private MainLayout MainLayout { get; set; } = null!; + + [Inject] + private IJSRuntime JsRuntime { get; init; } = null!; + + private ElementReference AnchorAfterChildContent { get; set; } #region Overrides of ComponentBase @@ -59,4 +64,9 @@ public partial class InnerScrolling : MSGComponentBase #endregion private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});"; + + public async Task ScrollToBottom() + { + await this.AnchorAfterChildContent.ScrollIntoViewAsync(this.JsRuntime); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index d8e2d99..beb5266 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -16,7 +16,7 @@ - + @if (this.chatThread is not null) { diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index 9d22184..b9b1c3f 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -29,6 +29,8 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable [Inject] public IDialogService DialogService { get; set; } = null!; + + private InnerScrolling scrollingArea = null!; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom; private static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); @@ -42,6 +44,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable private Guid currentWorkspaceId = Guid.Empty; private bool workspacesVisible; private Workspaces? workspaces; + private bool mustScrollToBottomAfterRender; // Unfortunately, we need the input field reference to clear it after sending a message. // This is necessary because we have to handle the key events ourselves. Otherwise, @@ -83,6 +86,17 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable await base.OnInitializedAsync(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(this.mustScrollToBottomAfterRender) + { + await this.scrollingArea.ScrollToBottom(); + this.mustScrollToBottomAfterRender = false; + } + + await base.OnAfterRenderAsync(firstRender); + } + #endregion private bool IsProviderSelected => this.providerSettings.UsedProvider != Providers.NONE; @@ -372,6 +386,11 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable this.currentWorkspaceName = this.chatThread is null ? string.Empty : await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId); await this.inputField.Clear(); + if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading) + { + this.mustScrollToBottomAfterRender = true; + this.StateHasChanged(); + } } private void ResetState() diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor index 4a7bd8c..68cad51 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor +++ b/app/MindWork AI Studio/Pages/Settings.razor @@ -77,6 +77,7 @@ + diff --git a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs index 825bfc9..4671d12 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs @@ -16,4 +16,9 @@ public sealed class DataChat /// Should we preselect a provider for the chat? /// public string PreselectedProvider { get; set; } = string.Empty; + + /// + /// Should we show the latest message after loading? When false, we show the first (aka oldest) message. + /// + public bool ShowLatestMessageAfterLoading { get; set; } = true; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ElementReferenceExtensions.cs b/app/MindWork AI Studio/Tools/ElementReferenceExtensions.cs new file mode 100644 index 0000000..fe46acf --- /dev/null +++ b/app/MindWork AI Studio/Tools/ElementReferenceExtensions.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Tools; + +public static class ElementReferenceExtensions +{ + public static async ValueTask ScrollIntoViewAsync(this ElementReference elementReference, IJSRuntime jsRuntime) + { + await jsRuntime.InvokeVoidAsync("scrollToBottom", elementReference); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/app.js b/app/MindWork AI Studio/wwwroot/app.js index 61a5241..42b43f0 100644 --- a/app/MindWork AI Studio/wwwroot/app.js +++ b/app/MindWork AI Studio/wwwroot/app.js @@ -21,4 +21,8 @@ window.generateDiff = function (text1, text2, divDiff, divLegend) { window.clearDiv = function (divName) { let targetDiv = document.getElementById(divName); targetDiv.innerHTML = ''; +} + +window.scrollToBottom = function(element) { + element.scrollIntoView(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.8.12.md b/app/MindWork AI Studio/wwwroot/changelog/v0.8.12.md index de1b9d9..6185dff 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.8.12.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.8.12.md @@ -1,7 +1,8 @@ # v0.8.12, build 174 - Added an e-mail writing assistant. - Added the possibility to preselect some e-mail writing assistant options. -- Improved: all assistants now have a button to copy their respective result to the clipboard. +- Improved chat page by scrolling to the bottom after loading (configurable; default is on). +- Improved all assistants to provide a button to copy their respective result to the clipboard. - Improved the content validation for the agenda assistant. - Improved the language handling of the agenda assistant. - Refactored the "send to" implementation of assistants. \ No newline at end of file