Added workspace display options (#184)

This commit is contained in:
Thorsten Sommer 2024-11-02 22:53:02 +01:00 committed by GitHub
parent bd97c0fb4b
commit d2ac4e26b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 185 additions and 78 deletions

View File

@ -1,6 +1,12 @@
@inherits MSGComponentBase @inherits MSGComponentBase
<div class="d-flex flex-column" style="@this.Height"> <div class="@this.Classes" style="@this.Styles">
@if (this.HeaderContent is not null)
{
<div>
@this.HeaderContent
</div>
}
<div class="flex-auto overflow-auto"> <div class="flex-auto overflow-auto">
@this.ChildContent @this.ChildContent

View File

@ -6,6 +6,9 @@ namespace AIStudio.Components;
public partial class InnerScrolling : MSGComponentBase public partial class InnerScrolling : MSGComponentBase
{ {
[Parameter]
public bool FillEntireHorizontalSpace { get; set; }
/// <summary> /// <summary>
/// Set the height of anything above the scrolling content; usually a header. /// Set the height of anything above the scrolling content; usually a header.
/// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like /// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like
@ -14,6 +17,9 @@ public partial class InnerScrolling : MSGComponentBase
[Parameter] [Parameter]
public string HeaderHeight { get; set; } = "3em"; public string HeaderHeight { get; set; } = "3em";
[Parameter]
public RenderFragment? HeaderContent { get; set; }
[Parameter] [Parameter]
public RenderFragment? ChildContent { get; set; } public RenderFragment? ChildContent { get; set; }
@ -23,6 +29,9 @@ public partial class InnerScrolling : MSGComponentBase
[Parameter] [Parameter]
public RenderFragment? FooterContent { get; set; } public RenderFragment? FooterContent { get; set; }
[Parameter]
public string Class { get; set; } = string.Empty;
[CascadingParameter] [CascadingParameter]
private MainLayout MainLayout { get; set; } = null!; private MainLayout MainLayout { get; set; } = null!;
@ -62,7 +71,9 @@ public partial class InnerScrolling : MSGComponentBase
#endregion #endregion
private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});"; private string Styles => this.FillEntireHorizontalSpace ? $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight}); overflow-x: auto; min-width: 0;" : $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight}); flex-shrink: 0;";
private string Classes => this.FillEntireHorizontalSpace ? $"{this.Class} d-flex flex-column flex-grow-1" : $"{this.Class} d-flex flex-column";
public async Task ScrollToBottom() public async Task ScrollToBottom()
{ {

View File

@ -1,4 +1,4 @@
<MudTreeView T="ITreeItem" Items="@this.treeItems" SelectionMode="SelectionMode.SingleSelection" Hover="@true" ExpandOnClick="@true"> <MudTreeView T="ITreeItem" Items="@this.treeItems" SelectionMode="SelectionMode.SingleSelection" Hover="@true" ExpandOnClick="@true" Class="ma-3">
<ItemTemplate Context="item"> <ItemTemplate Context="item">
@switch (item.Value) @switch (item.Value)
{ {
@ -11,7 +11,7 @@
case TreeItemData treeItem: case TreeItemData treeItem:
@if (treeItem.Type is TreeItemType.CHAT) @if (treeItem.Type is TreeItemType.CHAT)
{ {
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Path, true)"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" Expanded="@item.Expanded" CanExpand="@treeItem.Expandable" Items="@treeItem.Children" OnClick="() => this.LoadChat(treeItem.Path, true)">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;"> <MudText Style="justify-self: start;">
@ -44,7 +44,7 @@
} }
else if (treeItem.Type is TreeItemType.WORKSPACE) else if (treeItem.Type is TreeItemType.WORKSPACE)
{ {
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" Expanded="@item.Expanded" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">@treeItem.Text</MudText> <MudText Style="justify-self: start;">@treeItem.Text</MudText>
@ -63,7 +63,7 @@
} }
else else
{ {
<MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" CanExpand="@treeItem.Expandable" Items="@treeItem.Children"> <MudTreeViewItem T="ITreeItem" Icon="@treeItem.Icon" Value="@item.Value" Expanded="@item.Expanded" CanExpand="@treeItem.Expandable" Items="@treeItem.Children">
<BodyContent> <BodyContent>
<div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%"> <div style="display: grid; grid-template-columns: 1fr auto; align-items: center; width: 100%">
<MudText Style="justify-self: start;">@treeItem.Text</MudText> <MudText Style="justify-self: start;">@treeItem.Text</MudText>

View File

@ -35,6 +35,9 @@ public partial class Workspaces : ComponentBase
[Parameter] [Parameter]
public Func<Task> LoadedChatWasChanged { get; set; } = () => Task.CompletedTask; public Func<Task> LoadedChatWasChanged { get; set; } = () => Task.CompletedTask;
[Parameter]
public bool ExpandRootNodes { get; set; } = true;
private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom; private const Placement WORKSPACE_ITEM_TOOLTIP_PLACEMENT = Placement.Bottom;
public static readonly Guid WORKSPACE_ID_BIAS = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02"); public static readonly Guid WORKSPACE_ID_BIAS = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02");
@ -73,9 +76,9 @@ public partial class Workspaces : ComponentBase
private async Task LoadTreeItems() private async Task LoadTreeItems()
{ {
this.treeItems.Clear(); var workspacesNode = new TreeItemData<ITreeItem>
this.treeItems.Add(new TreeItemData<ITreeItem>
{ {
Expanded = this.ExpandRootNodes,
Expandable = true, Expandable = true,
Value = new TreeItemData Value = new TreeItemData
{ {
@ -87,16 +90,11 @@ public partial class Workspaces : ComponentBase
Path = "root", Path = "root",
Children = await this.LoadWorkspaces(), Children = await this.LoadWorkspaces(),
}, },
}); };
this.treeItems.Add(new TreeItemData<ITreeItem> var tempChatNode = new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeDivider(),
});
this.treeItems.Add(new TreeItemData<ITreeItem>
{ {
Expanded = this.ExpandRootNodes,
Expandable = true, Expandable = true,
Value = new TreeItemData Value = new TreeItemData
{ {
@ -108,7 +106,16 @@ public partial class Workspaces : ComponentBase
Path = "temp", Path = "temp",
Children = await this.LoadTemporaryChats(), Children = await this.LoadTemporaryChats(),
}, },
};
this.treeItems.Clear();
this.treeItems.Add(workspacesNode);
this.treeItems.Add(new TreeItemData<ITreeItem>
{
Expandable = false,
Value = new TreeDivider(),
}); });
this.treeItems.Add(tempChatNode);
} }
private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadTemporaryChats() private async Task<IReadOnlyCollection<TreeItemData<ITreeItem>>> LoadTemporaryChats()

View File

@ -16,7 +16,45 @@
</MudText> </MudText>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/> <ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
<InnerScrolling @ref="@this.scrollingArea" HeaderHeight="12.3em"> <MudStack Row="@true" Style="width: 100%; overflow: hidden;">
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR
&& !this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible)
{
<MudPaper Class="border border-solid rounded-lg">
<MudTooltip Text="Show your workspaces" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Size="Size.Large" Icon="@this.WorkspaceSidebarToggleIcon" OnClick="() => this.ToggleWorkspaceSidebar()"/>
</MudTooltip>
</MudPaper>
}
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
@if ((this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR && this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible) || this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE)
{
@if (this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_SIDEBAR && this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible)
{
<InnerScrolling HeaderHeight="12.3em" Class="border border-solid rounded-lg">
<HeaderContent>
<MudTooltip Text="Hide your workspaces" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Size="Size.Large" Icon="@this.WorkspaceSidebarToggleIcon" OnClick="() => this.ToggleWorkspaceSidebar()"/>
</MudTooltip>
</HeaderContent>
<ChildContent>
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" LoadedChatWasChanged="this.LoadedChatChanged"/>
</ChildContent>
</InnerScrolling>
}
else
{
<InnerScrolling HeaderHeight="12.3em" Class="border border-solid rounded-lg">
<ChildContent>
<Workspaces @ref="this.workspaces" @bind-CurrentChatThread="@this.chatThread" LoadedChatWasChanged="this.LoadedChatChanged"/>
</ChildContent>
</InnerScrolling>
}
}
}
<InnerScrolling FillEntireHorizontalSpace="@true" @ref="@this.scrollingArea" HeaderHeight="12.3em">
<ChildContent> <ChildContent>
@if (this.chatThread is not null) @if (this.chatThread is not null)
{ {
@ -34,10 +72,12 @@
<MudTextField T="string" @ref="@this.inputField" @bind-Text="@this.userInput" Variant="Variant.Outlined" AutoGrow="@true" Lines="3" MaxLines="12" Label="@this.InputLabel" Placeholder="@this.ProviderPlaceholder" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Send" OnAdornmentClick="() => this.SendMessage()" ReadOnly="!this.IsProviderSelected || this.isStreaming" Immediate="@true" OnKeyUp="this.InputKeyEvent" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="@this.UserInputClass" Style="@this.UserInputStyle"/> <MudTextField T="string" @ref="@this.inputField" @bind-Text="@this.userInput" Variant="Variant.Outlined" AutoGrow="@true" Lines="3" MaxLines="12" Label="@this.InputLabel" Placeholder="@this.ProviderPlaceholder" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Send" OnAdornmentClick="() => this.SendMessage()" ReadOnly="!this.IsProviderSelected || this.isStreaming" Immediate="@true" OnKeyUp="this.InputKeyEvent" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="@this.UserInputClass" Style="@this.UserInputStyle"/>
</MudElement> </MudElement>
<MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey;"> <MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey;">
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES) @if (
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
{ {
<MudTooltip Text="Your workspaces" Placement="@TOOLBAR_TOOLTIP_PLACEMENT"> <MudTooltip Text="Show your workspaces" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.SnippetFolder" OnClick="() => this.ToggleWorkspaces()"/> <MudIconButton Icon="@Icons.Material.Filled.SnippetFolder" OnClick="() => this.ToggleWorkspaceOverlay()"/>
</MudTooltip> </MudTooltip>
} }
@ -81,17 +121,20 @@
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" /> <ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" />
</MudToolBar> </MudToolBar>
</FooterContent> </FooterContent>
</InnerScrolling> </InnerScrolling>
</MudStack>
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior != WorkspaceStorageBehavior.DISABLE_WORKSPACES) @if (
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior != WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
{ {
<MudDrawer @bind-Open="@this.workspacesVisible" Width="40em" Height="100%" Anchor="Anchor.Start" Variant="DrawerVariant.Temporary" Elevation="1"> <MudDrawer @bind-Open="@this.workspaceOverlayVisible" Width="40em" Height="100%" Anchor="Anchor.Start" Variant="DrawerVariant.Temporary" Elevation="1">
<MudDrawerHeader> <MudDrawerHeader>
<MudStack Row="@true" AlignItems="AlignItems.Center"> <MudStack Row="@true" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h6" Class="mr-3"> <MudText Typo="Typo.h6" Class="mr-3">
Your workspaces Your workspaces
</MudText> </MudText>
<MudIconButton Icon="@Icons.Material.Filled.Close" Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" OnClick="() => this.ToggleWorkspaces()"/> <MudIconButton Icon="@Icons.Material.Filled.Close" Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" OnClick="() => this.ToggleWorkspaceOverlay()"/>
</MudStack> </MudStack>
</MudDrawerHeader> </MudDrawerHeader>
<MudDrawerContainer Class="ml-6"> <MudDrawerContainer Class="ml-6">

View File

@ -42,7 +42,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
private string userInput = string.Empty; private string userInput = string.Empty;
private string currentWorkspaceName = string.Empty; private string currentWorkspaceName = string.Empty;
private Guid currentWorkspaceId = Guid.Empty; private Guid currentWorkspaceId = Guid.Empty;
private bool workspacesVisible; private bool workspaceOverlayVisible;
private Workspaces? workspaces; private Workspaces? workspaces;
private bool mustScrollToBottomAfterRender; private bool mustScrollToBottomAfterRender;
private bool mustStoreChat; private bool mustStoreChat;
@ -158,6 +158,8 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty; private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty;
private string WorkspaceSidebarToggleIcon => this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible ? Icons.Material.Filled.ArrowCircleLeft : Icons.Material.Filled.ArrowCircleRight;
private void ProfileWasChanged(Profile profile) private void ProfileWasChanged(Profile profile)
{ {
this.currentProfile = profile; this.currentProfile = profile;
@ -308,9 +310,15 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
} }
} }
private void ToggleWorkspaces() private void ToggleWorkspaceOverlay()
{ {
this.workspacesVisible = !this.workspacesVisible; this.workspaceOverlayVisible = !this.workspaceOverlayVisible;
}
private async Task ToggleWorkspaceSidebar()
{
this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible = !this.SettingsManager.ConfigurationData.Workspace.IsSidebarVisible;
await this.SettingsManager.StoreSettings();
} }
private async Task SaveThread() private async Task SaveThread()

View File

@ -202,7 +202,12 @@
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Work" HeaderText="Workspace Options"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Work" HeaderText="Workspace Options">
<ConfigurationSelect OptionDescription="Workspace behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior = selectedValue)" OptionHelp="Should we store your chats?"/> <ConfigurationSelect OptionDescription="Workspace behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageBehavior = selectedValue)" OptionHelp="Should we store your chats?"/>
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES)
{
<ConfigurationSelect OptionDescription="Workspace maintenance" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageTemporaryMaintenancePolicyData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy = selectedValue)" OptionHelp="If and when should we delete your temporary chats?"/> <ConfigurationSelect OptionDescription="Workspace maintenance" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy)" Data="@ConfigurationSelectDataFactory.GetWorkspaceStorageTemporaryMaintenancePolicyData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.StorageTemporaryMaintenancePolicy = selectedValue)" OptionHelp="If and when should we delete your temporary chats?"/>
<ConfigurationSelect OptionDescription="Workspace display behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior)" Data="@ConfigurationSelectDataFactory.GetWorkspaceDisplayBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior = selectedValue)" OptionHelp="How should we display your workspaces?"/>
}
</ExpansionPanel> </ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.FindInPage" HeaderText="Assistant: Icon Finder Options"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.FindInPage" HeaderText="Assistant: Icon Finder Options">

View File

@ -58,6 +58,13 @@ public static class ConfigurationSelectDataFactory
yield return new("Delete temporary chats older than 1 year", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS); yield return new("Delete temporary chats older than 1 year", WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_365_DAYS);
} }
public static IEnumerable<ConfigurationSelectData<WorkspaceDisplayBehavior>> GetWorkspaceDisplayBehaviorData()
{
yield return new("Toggle the overlay: the chat uses all the space, workspaces are temporarily shown", WorkspaceDisplayBehavior.TOGGLE_OVERLAY);
yield return new("Toggle the sidebar: show the workspaces next to the chat when desired", WorkspaceDisplayBehavior.TOGGLE_SIDEBAR);
yield return new("Sidebar is always visible: show the workspaces next to the chat all the time", WorkspaceDisplayBehavior.SIDEBAR_ALWAYS_VISIBLE);
}
public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData() public static IEnumerable<ConfigurationSelectData<NavBehavior>> GetNavBehaviorData()
{ {
yield return new("Navigation expands on mouse hover", NavBehavior.EXPAND_ON_HOVER); yield return new("Navigation expands on mouse hover", NavBehavior.EXPAND_ON_HOVER);

View File

@ -11,4 +11,14 @@ public sealed class DataWorkspace
/// The chat storage maintenance behavior. /// The chat storage maintenance behavior.
/// </summary> /// </summary>
public WorkspaceStorageTemporaryMaintenancePolicy StorageTemporaryMaintenancePolicy { get; set; } = WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS; public WorkspaceStorageTemporaryMaintenancePolicy StorageTemporaryMaintenancePolicy { get; set; } = WorkspaceStorageTemporaryMaintenancePolicy.DELETE_OLDER_THAN_90_DAYS;
/// <summary>
/// The behavior used for displaying the workspace.
/// </summary>
public WorkspaceDisplayBehavior DisplayBehavior { get; set; } = WorkspaceDisplayBehavior.TOGGLE_SIDEBAR;
/// <summary>
/// Indicates whether the sidebar is currently visible.
/// </summary>
public bool IsSidebarVisible { get; set; } = true;
} }

View File

@ -0,0 +1,9 @@
namespace AIStudio.Settings.DataModel;
public enum WorkspaceDisplayBehavior
{
TOGGLE_OVERLAY,
TOGGLE_SIDEBAR,
SIDEBAR_ALWAYS_VISIBLE,
}

View File

@ -1,4 +1,5 @@
# v0.9.16, build 191 (2024-11-xx xx:xx UTC) # v0.9.16, build 191 (2024-11-xx xx:xx UTC)
- Added our second financial contributor: Thanks `peerschuett` for supporting AI Studio financially. - Added our second financial contributor: Thanks `peerschuett` for supporting AI Studio financially.
- Added multiple workspace display options: toggle the workspace overlay (this is the previous behavior), toggle the workspace sidebar, or permanently display the workspace sidebar.
- Improved the layout of the app window, for example, in full-screen mode on large or high-resolution monitors. - Improved the layout of the app window, for example, in full-screen mode on large or high-resolution monitors.
- Fixed the "send to" issue, where you could select the bias-of-the-day assistant as destination. - Fixed the "send to" issue, where you could select the bias-of-the-day assistant as destination.