Improved chat page by scrolling to the bottom after loading (#87)

This commit is contained in:
Thorsten Sommer 2024-08-23 10:32:27 +02:00 committed by GitHub
parent 3409872090
commit 1b3fff67ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 57 additions and 2 deletions

View File

@ -3,6 +3,10 @@
<div class="d-flex flex-column" style="@this.Height"> <div class="d-flex flex-column" style="@this.Height">
<div class="flex-auto overflow-auto"> <div class="flex-auto overflow-auto">
@this.ChildContent @this.ChildContent
<div @ref="@this.AnchorAfterChildContent">
&nbsp;
</div>
</div> </div>
@this.FooterContent @this.FooterContent

View File

@ -27,6 +27,11 @@ public partial class InnerScrolling : MSGComponentBase
[CascadingParameter] [CascadingParameter]
private MainLayout MainLayout { get; set; } = null!; private MainLayout MainLayout { get; set; } = null!;
[Inject]
private IJSRuntime JsRuntime { get; init; } = null!;
private ElementReference AnchorAfterChildContent { get; set; }
#region Overrides of ComponentBase #region Overrides of ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@ -59,4 +64,9 @@ public partial class InnerScrolling : MSGComponentBase
#endregion #endregion
private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});"; private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});";
public async Task ScrollToBottom()
{
await this.AnchorAfterChildContent.ScrollIntoViewAsync(this.JsRuntime);
}
} }

View File

@ -16,7 +16,7 @@
</MudText> </MudText>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/> <ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
<InnerScrolling HeaderHeight="12.3em"> <InnerScrolling @ref="@this.scrollingArea" HeaderHeight="12.3em">
<ChildContent> <ChildContent>
@if (this.chatThread is not null) @if (this.chatThread is not null)
{ {

View File

@ -30,6 +30,8 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
[Inject] [Inject]
public IDialogService DialogService { get; set; } = null!; public IDialogService DialogService { get; set; } = null!;
private InnerScrolling scrollingArea = null!;
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Bottom;
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new(); private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
@ -42,6 +44,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
private Guid currentWorkspaceId = Guid.Empty; private Guid currentWorkspaceId = Guid.Empty;
private bool workspacesVisible; private bool workspacesVisible;
private Workspaces? workspaces; private Workspaces? workspaces;
private bool mustScrollToBottomAfterRender;
// Unfortunately, we need the input field reference to clear it after sending a message. // 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, // 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(); 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 #endregion
private bool IsProviderSelected => this.providerSettings.UsedProvider != Providers.NONE; 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); this.currentWorkspaceName = this.chatThread is null ? string.Empty : await this.workspaces.LoadWorkspaceName(this.chatThread.WorkspaceId);
await this.inputField.Clear(); await this.inputField.Clear();
if (this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)
{
this.mustScrollToBottomAfterRender = true;
this.StateHasChanged();
}
} }
private void ResetState() private void ResetState()

View File

@ -77,6 +77,7 @@
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Chat" HeaderText="Chat Options">
<ConfigurationSelect OptionDescription="Shortcut to send input" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior)" Data="@ConfigurationSelectDataFactory.GetSendBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior = selectedValue)" OptionHelp="Do you want to use any shortcut to send your input?"/> <ConfigurationSelect OptionDescription="Shortcut to send input" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior)" Data="@ConfigurationSelectDataFactory.GetSendBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.ShortcutSendBehavior = selectedValue)" OptionHelp="Do you want to use any shortcut to send your input?"/>
<ConfigurationOption OptionDescription="Show the latest message after loading?" LabelOn="Latest message is shown, after loading a chat" LabelOff="First (oldest) message is shown, after loading a chat" State="@(() => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.ShowLatestMessageAfterLoading = updatedState)" OptionHelp="When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown."/>
<ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/> <ConfigurationOption OptionDescription="Preselect chat options?" LabelOn="Chat options are preselected" LabelOff="No chat options are preselected" State="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.Chat.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider."/>
<ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/> <ConfigurationProviderSelection Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.Chat.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Chat.PreselectedProvider = selectedValue)"/>
</ExpansionPanel> </ExpansionPanel>

View File

@ -16,4 +16,9 @@ public sealed class DataChat
/// Should we preselect a provider for the chat? /// Should we preselect a provider for the chat?
/// </summary> /// </summary>
public string PreselectedProvider { get; set; } = string.Empty; public string PreselectedProvider { get; set; } = string.Empty;
/// <summary>
/// Should we show the latest message after loading? When false, we show the first (aka oldest) message.
/// </summary>
public bool ShowLatestMessageAfterLoading { get; set; } = true;
} }

View File

@ -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);
}
}

View File

@ -22,3 +22,7 @@ window.clearDiv = function (divName) {
let targetDiv = document.getElementById(divName); let targetDiv = document.getElementById(divName);
targetDiv.innerHTML = ''; targetDiv.innerHTML = '';
} }
window.scrollToBottom = function(element) {
element.scrollIntoView();
}

View File

@ -1,7 +1,8 @@
# v0.8.12, build 174 # v0.8.12, build 174
- Added an e-mail writing assistant. - Added an e-mail writing assistant.
- Added the possibility to preselect some e-mail writing assistant options. - 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 content validation for the agenda assistant.
- Improved the language handling of the agenda assistant. - Improved the language handling of the agenda assistant.
- Refactored the "send to" implementation of assistants. - Refactored the "send to" implementation of assistants.