Added color themes (dark & light) (#148)

This commit is contained in:
Thorsten Sommer 2024-09-15 12:30:07 +02:00 committed by GitHub
parent 0ad7b8d4dd
commit e9998348c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 350 additions and 50 deletions

View File

@ -45,7 +45,7 @@
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO)) @if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
{ {
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Color="Color.Info"> <MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
@foreach (var assistant in Enum.GetValues<Components>().OrderBy(n => n.Name().Length)) @foreach (var assistant in Enum.GetValues<Components>().OrderBy(n => n.Name().Length))
{ {
if (assistant is Components.NONE || this.Component == assistant) if (assistant is Components.NONE || this.Component == assistant)
@ -77,7 +77,7 @@
break; break;
case SendToButton sendToButton: case SendToButton sendToButton:
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Color="Color.Info"> <MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
@foreach (var assistant in Enum.GetValues<Components>().OrderBy(n => n.Name().Length)) @foreach (var assistant in Enum.GetValues<Components>().OrderBy(n => n.Name().Length))
{ {
if(assistant is Components.NONE || sendToButton.Self == assistant) if(assistant is Components.NONE || sendToButton.Self == assistant)
@ -96,7 +96,7 @@
Copy result Copy result
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()"> <MudButton Variant="Variant.Filled" Style="@this.GetResetColor()" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
Reset Reset
</MudButton> </MudButton>

View File

@ -8,7 +8,7 @@ using RustService = AIStudio.Tools.RustService;
namespace AIStudio.Assistants; namespace AIStudio.Assistants;
public abstract partial class AssistantBase : ComponentBase public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver, IDisposable
{ {
[Inject] [Inject]
protected SettingsManager SettingsManager { get; init; } = null!; protected SettingsManager SettingsManager { get; init; } = null!;
@ -31,6 +31,12 @@ public abstract partial class AssistantBase : ComponentBase
[Inject] [Inject]
protected ILogger<AssistantBase> Logger { get; init; } = null!; protected ILogger<AssistantBase> Logger { get; init; } = null!;
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
[Inject]
private MessageBus MessageBus { get; init; } = null!;
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult"; internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
internal const string RESULT_DIV_ID = "assistantResult"; internal const string RESULT_DIV_ID = "assistantResult";
@ -91,6 +97,10 @@ public abstract partial class AssistantBase : ComponentBase
this.MightPreselectValues(); this.MightPreselectValues();
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component); this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component); this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
@ -114,7 +124,28 @@ public abstract partial class AssistantBase : ComponentBase
#endregion #endregion
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder() : string.Empty; #region Implementation of IMessageBusReceiver
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.COLOR_THEME_CHANGED:
this.StateHasChanged();
break;
}
return Task.CompletedTask;
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}
#endregion
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
protected string? ValidatingProvider(AIStudio.Settings.Provider provider) protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
{ {
@ -251,4 +282,25 @@ public abstract partial class AssistantBase : ComponentBase
this.StateHasChanged(); this.StateHasChanged();
this.form?.ResetValidation(); this.form?.ResetValidation();
} }
private string GetResetColor() => this.SettingsManager.IsDarkMode switch
{
true => $"background-color: #804000",
false => $"background-color: {this.ColorTheme.GetCurrentPalette(this.SettingsManager).Warning.Value}",
};
private string GetSendToColor() => this.SettingsManager.IsDarkMode switch
{
true => $"background-color: #004080",
false => $"background-color: {this.ColorTheme.GetCurrentPalette(this.SettingsManager).InfoLighten}",
};
#region Implementation of IDisposable
public void Dispose()
{
this.MessageBus.Unregister(this);
}
#endregion
} }

View File

@ -1,4 +1,4 @@
<MudCard Outlined="@true" Style="border-width: 2px; border-color: #0d47a1; border-radius: 12px; border-style: solid; max-width: 20em;"> <MudCard Outlined="@true" Style="@this.BlockStyle">
<MudCardHeader> <MudCardHeader>
<CardHeaderContent> <CardHeaderContent>
<MudStack AlignItems="AlignItems.Center" Row="@true"> <MudStack AlignItems="AlignItems.Center" Row="@true">

View File

@ -1,8 +1,10 @@
using AIStudio.Settings;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace AIStudio.Components; namespace AIStudio.Components;
public partial class AssistantBlock : ComponentBase public partial class AssistantBlock : ComponentBase, IMessageBusReceiver, IDisposable
{ {
[Parameter] [Parameter]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
@ -18,4 +20,63 @@ public partial class AssistantBlock : ComponentBase
[Parameter] [Parameter]
public string Link { get; set; } = string.Empty; public string Link { get; set; } = string.Empty;
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
[Inject]
private MessageBus MessageBus { get; init; } = null!;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
await base.OnInitializedAsync();
}
#endregion
#region Implementation of IMessageBusReceiver
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.COLOR_THEME_CHANGED:
this.StateHasChanged();
break;
}
return Task.CompletedTask;
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}
#endregion
private string BorderColor => this.SettingsManager.IsDarkMode switch
{
true => this.ColorTheme.GetCurrentPalette(this.SettingsManager).GrayLight,
false => this.ColorTheme.GetCurrentPalette(this.SettingsManager).Primary.Value,
};
private string BlockStyle => $"border-width: 2px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;";
#region Implementation of IDisposable
public void Dispose()
{
this.MessageBus.Unregister(this);
}
#endregion
} }

View File

@ -3,11 +3,11 @@
<MudTooltip Text="Shows and hides the confidence card with information about the selected LLM provider."> <MudTooltip Text="Shows and hides the confidence card with information about the selected LLM provider.">
@if (this.Mode is ConfidenceInfoMode.ICON) @if (this.Mode is ConfidenceInfoMode.ICON)
{ {
<MudIconButton Icon="@Icons.Material.Filled.Security" Class="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle()" OnClick="@(() => this.ToggleConfidence())"/> <MudIconButton Icon="@Icons.Material.Filled.Security" Class="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager)" OnClick="@(() => this.ToggleConfidence())"/>
} }
else else
{ {
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Security" IconClass="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle()" OnClick="@(() => this.ToggleConfidence())"> <MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Security" IconClass="confidence-icon" Style="@this.LLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager)" OnClick="@(() => this.ToggleConfidence())">
Confidence Confidence
</MudButton> </MudButton>
} }

View File

@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Components; namespace AIStudio.Components;
public partial class ConfidenceInfo : ComponentBase public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDisposable
{ {
[Parameter] [Parameter]
public ConfidenceInfoMode Mode { get; set; } = ConfidenceInfoMode.BUTTON; public ConfidenceInfoMode Mode { get; set; } = ConfidenceInfoMode.BUTTON;
@ -16,6 +16,9 @@ public partial class ConfidenceInfo : ComponentBase
[Inject] [Inject]
private SettingsManager SettingsManager { get; init; } = null!; private SettingsManager SettingsManager { get; init; } = null!;
[Inject]
private MessageBus MessageBus { get; init; } = null!;
private Confidence currentConfidence; private Confidence currentConfidence;
private bool showConfidence; private bool showConfidence;
@ -28,6 +31,9 @@ public partial class ConfidenceInfo : ComponentBase
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
this.currentConfidence = this.LLMProvider.GetConfidence(this.SettingsManager); this.currentConfidence = this.LLMProvider.GetConfidence(this.SettingsManager);
await base.OnParametersSetAsync(); await base.OnParametersSetAsync();
} }
@ -51,7 +57,38 @@ public partial class ConfidenceInfo : ComponentBase
yield return ($"Source {++index}", source); yield return ($"Source {++index}", source);
} }
private string GetCurrentConfidenceColor() => $"color: {this.currentConfidence.Level.GetColor()};"; private string GetCurrentConfidenceColor() => $"color: {this.currentConfidence.Level.GetColor(this.SettingsManager)};";
private string GetPopoverStyle() => $"border-color: {this.currentConfidence.Level.GetColor()}; max-width: calc(35vw);"; private string GetPopoverStyle() => $"border-color: {this.currentConfidence.Level.GetColor(this.SettingsManager)}; max-width: calc(35vw);";
#region Implementation of IMessageBusReceiver
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.COLOR_THEME_CHANGED:
this.showConfidence = false;
this.StateHasChanged();
break;
}
return Task.CompletedTask;
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}
#endregion
#region Implementation of IDisposable
public void Dispose()
{
this.MessageBus.Unregister(this);
}
#endregion
} }

View File

@ -43,7 +43,7 @@ public partial class InnerScrolling : MSGComponentBase
#region Overrides of MSGComponentBase #region Overrides of MSGComponentBase
public override Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{ {
switch (triggeredEvent) switch (triggeredEvent)
{ {

View File

@ -19,7 +19,22 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus
#region Implementation of IMessageBusReceiver #region Implementation of IMessageBusReceiver
public abstract Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data); public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.COLOR_THEME_CHANGED:
this.StateHasChanged();
break;
default:
return this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
}
return Task.CompletedTask;
}
public abstract Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
public abstract Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data); public abstract Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data);
@ -46,6 +61,12 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus
protected void ApplyFilters(ComponentBase[] components, Event[] events) protected void ApplyFilters(ComponentBase[] components, Event[] events)
{ {
this.MessageBus.ApplyFilters(this, components, events); // Append the color theme changed event to the list of events:
var eventsList = new List<Event>(events)
{
Event.COLOR_THEME_CHANGED
};
this.MessageBus.ApplyFilters(this, components, eventsList.ToArray());
} }
} }

View File

@ -11,17 +11,17 @@
<MudDrawerContainer Class="mud-height-full absolute"> <MudDrawerContainer Class="mud-height-full absolute">
<MudDrawer @bind-Open="@this.navBarOpen" MiniWidth="@NAVBAR_COLLAPSED_WIDTH" Width="@NAVBAR_EXPANDED_WIDTH" Elevation="1" Fixed="@true" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.EXPAND_ON_HOVER)" Color="Color.Default"> <MudDrawer @bind-Open="@this.navBarOpen" MiniWidth="@NAVBAR_COLLAPSED_WIDTH" Width="@NAVBAR_EXPANDED_WIDTH" Elevation="1" Fixed="@true" Variant="@DrawerVariant.Mini" OpenMiniOnHover="@(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.EXPAND_ON_HOVER)" Color="Color.Default">
<MudNavMenu> <MudNavMenu>
@foreach (var navBarItem in NAV_ITEMS) @foreach (var navBarItem in this.navItems)
{ {
if (this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS) if (this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS)
{ {
<MudTooltip Text="@navBarItem.Name" Placement="Placement.Right"> <MudTooltip Text="@navBarItem.Name" Placement="Placement.Right">
<MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink> <MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" Style="@navBarItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color">@navBarItem.Name</MudNavLink>
</MudTooltip> </MudTooltip>
} }
else else
{ {
<MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" IconColor="@navBarItem.IconColor">@navBarItem.Name</MudNavLink> <MudNavLink Href="@navBarItem.Path" Match="@(navBarItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@navBarItem.Icon" Style="@navBarItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color">@navBarItem.Name</MudNavLink>
} }
} }
</MudNavMenu> </MudNavMenu>
@ -59,3 +59,5 @@
</MudMainContent> </MudMainContent>
</MudLayout> </MudLayout>
</MudPaper> </MudPaper>
<MudThemeProvider @ref="@this.themeProvider" Theme="@this.ColorTheme" IsDarkMode="@this.useDarkMode" />

View File

@ -35,6 +35,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
[Inject] [Inject]
private ILogger<MainLayout> Logger { get; init; } = null!; private ILogger<MainLayout> Logger { get; init; } = null!;
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
public string AdditionalHeight { get; private set; } = "0em"; public string AdditionalHeight { get; private set; } = "0em";
private string PaddingLeft => this.navBarOpen ? $"padding-left: {NAVBAR_EXPANDED_WIDTH_INT - NAVBAR_COLLAPSED_WIDTH_INT}em;" : "padding-left: 0em;"; private string PaddingLeft => this.navBarOpen ? $"padding-left: {NAVBAR_EXPANDED_WIDTH_INT - NAVBAR_COLLAPSED_WIDTH_INT}em;" : "padding-left: 0em;";
@ -50,16 +53,10 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
private bool userDismissedUpdate; private bool userDismissedUpdate;
private string updateToVersion = string.Empty; private string updateToVersion = string.Empty;
private UpdateResponse? currentUpdateResponse; private UpdateResponse? currentUpdateResponse;
private MudThemeProvider themeProvider = null!;
private bool useDarkMode;
private static readonly IReadOnlyCollection<NavBarItem> NAV_ITEMS = new List<NavBarItem> private IReadOnlyCollection<NavBarItem> navItems = [];
{
new("Home", Icons.Material.Filled.Home, Color.Default, Routes.HOME, true),
new("Chat", Icons.Material.Filled.Chat, Color.Default, Routes.CHAT, false),
new("Assistants", Icons.Material.Filled.Apps, Color.Default, Routes.ASSISTANTS, false),
new("Supporters", Icons.Material.Filled.Favorite, Color.Error, Routes.SUPPORTERS, false),
new("About", Icons.Material.Filled.Info, Color.Default, Routes.ABOUT, false),
new("Settings", Icons.Material.Filled.Settings, Color.Default, Routes.SETTINGS, false),
};
#region Overrides of ComponentBase #region Overrides of ComponentBase
@ -87,7 +84,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
// Register this component with the message bus: // Register this component with the message bus:
this.MessageBus.RegisterComponent(this); this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED ]); this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED ]);
// Set the snackbar for the update service: // Set the snackbar for the update service:
UpdateService.SetBlazorDependencies(this.Snackbar); UpdateService.SetBlazorDependencies(this.Snackbar);
@ -97,6 +94,20 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
if(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.ALWAYS_EXPAND) if(this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.ALWAYS_EXPAND)
this.navBarOpen = true; this.navBarOpen = true;
await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged);
await this.UpdateThemeConfiguration();
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
this.navItems = new List<NavBarItem>
{
new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true),
new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false),
new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false),
new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false),
new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false),
new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false),
};
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
@ -131,6 +142,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
else else
this.navBarOpen = false; this.navBarOpen = false;
await this.UpdateThemeConfiguration();
this.StateHasChanged();
break;
case Event.COLOR_THEME_CHANGED:
this.StateHasChanged(); this.StateHasChanged();
break; break;
} }
@ -218,6 +234,24 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis
} }
} }
private async Task SystemeThemeChanged(bool isDark)
{
this.Logger.LogInformation($"The system theme changed to {(isDark ? "dark" : "light")}.");
await this.UpdateThemeConfiguration();
}
private async Task UpdateThemeConfiguration()
{
if (this.SettingsManager.ConfigurationData.App.PreferredTheme is Themes.SYSTEM)
this.useDarkMode = await this.themeProvider.GetSystemPreference();
else
this.useDarkMode = this.SettingsManager.ConfigurationData.App.PreferredTheme == Themes.DARK;
this.SettingsManager.IsDarkMode = this.useDarkMode;
await this.MessageBus.SendMessage<bool>(this, Event.COLOR_THEME_CHANGED);
this.StateHasChanged();
}
#region Implementation of IDisposable #region Implementation of IDisposable
public void Dispose() public void Dispose()

View File

@ -1,3 +1,8 @@
using AIStudio.Settings;
namespace AIStudio.Layout; namespace AIStudio.Layout;
public record NavBarItem(string Name, string Icon, Color IconColor, string Path, bool MatchAll); public record NavBarItem(string Name, string Icon, string IconLightColor, string IconDarkColor, string Path, bool MatchAll)
{
public string SetColorStyle(SettingsManager settingsManager) => $"--custom-icon-color: {(settingsManager.IsDarkMode ? this.IconDarkColor : this.IconLightColor)};";
}

View File

@ -120,7 +120,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
private string TooltipAddChatToWorkspace => $"Start new chat in workspace \"{this.currentWorkspaceName}\""; private string TooltipAddChatToWorkspace => $"Start new chat in workspace \"{this.currentWorkspaceName}\"";
private string UserInputStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle() : string.Empty; private string UserInputStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty;
private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty; private string UserInputClass => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? "confidence-border" : string.Empty;
@ -455,7 +455,7 @@ public partial class Chat : MSGComponentBase, IAsyncDisposable
#region Overrides of MSGComponentBase #region Overrides of MSGComponentBase
public override Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{ {
switch (triggeredEvent) switch (triggeredEvent)
{ {

View File

@ -180,6 +180,7 @@
</ExpansionPanel> </ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options">
<ConfigurationSelect OptionDescription="Color theme" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="Choose the color theme that best suits for you."/>
<ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/> <ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/>
<ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." /> <ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." />
<ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/> <ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/>

View File

@ -171,9 +171,9 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable
private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider) private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider)
{ {
if (this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme.TryGetValue(llmProvider, out var level)) if (this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme.TryGetValue(llmProvider, out var level))
return $"background-color: {level.GetColor()};"; return $"background-color: {level.GetColor(this.SettingsManager)};";
return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor()};"; return $"background-color: {ConfidenceLevel.UNKNOWN.GetColor(this.SettingsManager)};";
} }
private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level) private async Task ChangeCustomConfidenceLevel(LLMProviders llmProvider, ConfidenceLevel level)

View File

@ -106,6 +106,7 @@ internal sealed class Program
}); });
builder.Services.AddMudMarkdownServices(); builder.Services.AddMudMarkdownServices();
builder.Services.AddSingleton(new MudTheme());
builder.Services.AddSingleton(MessageBus.INSTANCE); builder.Services.AddSingleton(MessageBus.INSTANCE);
builder.Services.AddSingleton(rust); builder.Services.AddSingleton(rust);
builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>(); builder.Services.AddMudMarkdownClipboardService<MarkdownClipboardService>();

View File

@ -1,3 +1,5 @@
using AIStudio.Settings;
namespace AIStudio.Provider; namespace AIStudio.Provider;
public sealed record Confidence public sealed record Confidence
@ -20,9 +22,9 @@ public sealed record Confidence
public Confidence WithLevel(ConfidenceLevel level) => this with { Level = level }; public Confidence WithLevel(ConfidenceLevel level) => this with { Level = level };
public string StyleBorder() => $"border: 2px solid {this.Level.GetColor()}; border-radius: 6px;"; public string StyleBorder(SettingsManager settingsManager) => $"border: 2px solid {this.Level.GetColor(settingsManager)}; border-radius: 6px;";
public string SetColorStyle() => $"--confidence-color: {this.Level.GetColor()};"; public string SetColorStyle(SettingsManager settingsManager) => $"--confidence-color: {this.Level.GetColor(settingsManager)};";
public static readonly Confidence NONE = new() public static readonly Confidence NONE = new()
{ {

View File

@ -1,3 +1,5 @@
using AIStudio.Settings;
namespace AIStudio.Provider; namespace AIStudio.Provider;
public static class ConfidenceLevelExtensions public static class ConfidenceLevelExtensions
@ -16,19 +18,31 @@ public static class ConfidenceLevelExtensions
_ => "Unknown confidence level", _ => "Unknown confidence level",
}; };
public static string GetColor(this ConfidenceLevel level) => level switch public static string GetColor(this ConfidenceLevel level, SettingsManager settingsManager) => (level, settingsManager.IsDarkMode) switch
{ {
ConfidenceLevel.NONE => "#cccccc", (ConfidenceLevel.NONE, _) => "#cccccc",
ConfidenceLevel.UNTRUSTED => "#ff0000", (ConfidenceLevel.UNTRUSTED, false) => "#ff0000",
ConfidenceLevel.VERY_LOW => "#ff6600", (ConfidenceLevel.UNTRUSTED, true) => "#800000",
ConfidenceLevel.LOW => "#ffcc00",
ConfidenceLevel.MODERATE => "#99cc00",
ConfidenceLevel.MEDIUM => "#86b300",
ConfidenceLevel.HIGH => "#009933",
_ => "#cc6600", (ConfidenceLevel.VERY_LOW, false) => "#ff6600",
(ConfidenceLevel.VERY_LOW, true) => "#803300",
(ConfidenceLevel.LOW, false) => "#ffcc00",
(ConfidenceLevel.LOW, true) => "#806600",
(ConfidenceLevel.MODERATE, false) => "#99cc00",
(ConfidenceLevel.MODERATE, true) => "#4d6600",
(ConfidenceLevel.MEDIUM, false) => "#86b300",
(ConfidenceLevel.MEDIUM, true) => "#394d00",
(ConfidenceLevel.HIGH, false) => "#009933",
(ConfidenceLevel.HIGH, true) => "#004d1a",
(_, false) => "#cc6600",
(_, true) => "#663300",
}; };
public static string SetColorStyle(this ConfidenceLevel level) => $"--confidence-color: {level.GetColor()};"; public static string SetColorStyle(this ConfidenceLevel level, SettingsManager settingsManager) => $"--confidence-color: {level.GetColor(settingsManager)};";
} }

View File

@ -8,7 +8,6 @@
</Found> </Found>
</Router> </Router>
<MudThemeProvider />
<MudDialogProvider /> <MudDialogProvider />
<MudPopoverProvider /> <MudPopoverProvider />
<MudSnackbarProvider /> <MudSnackbarProvider />

View File

@ -170,4 +170,10 @@ public static class ConfigurationSelectDataFactory
} }
} }
} }
public static IEnumerable<ConfigurationSelectData<Themes>> GetThemesData()
{
foreach (var theme in Enum.GetValues<Themes>())
yield return new(theme.GetName(), theme);
}
} }

View File

@ -2,6 +2,11 @@ namespace AIStudio.Settings.DataModel;
public sealed class DataApp public sealed class DataApp
{ {
/// <summary>
/// The preferred theme to use.
/// </summary>
public Themes PreferredTheme { get; set; } = Themes.SYSTEM;
/// <summary> /// <summary>
/// Should we save energy? When true, we will update content streamed /// Should we save energy? When true, we will update content streamed
/// from the server, i.e., AI, less frequently. /// from the server, i.e., AI, less frequently.

View File

@ -0,0 +1,8 @@
namespace AIStudio.Settings.DataModel;
public enum Themes
{
SYSTEM = 0,
LIGHT,
DARK,
}

View File

@ -0,0 +1,16 @@
namespace AIStudio.Settings.DataModel;
public static class ThemesExtensions
{
public static string GetName(this Themes theme)
{
return theme switch
{
Themes.SYSTEM => "Synchronized with the operating system settings",
Themes.LIGHT => "Always use light theme",
Themes.DARK => "Always use dark theme",
_ => "Unknown setting",
};
}
}

View File

@ -21,7 +21,7 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
Converters = { new JsonStringEnumConverter() }, Converters = { new JsonStringEnumConverter() },
}; };
private ILogger<SettingsManager> logger = logger; private readonly ILogger<SettingsManager> logger = logger;
/// <summary> /// <summary>
/// The directory where the configuration files are stored. /// The directory where the configuration files are stored.
@ -33,6 +33,11 @@ public sealed class SettingsManager(ILogger<SettingsManager> logger)
/// </summary> /// </summary>
public static string? DataDirectory { get; set; } public static string? DataDirectory { get; set; }
/// <summary>
/// Whether the app is in dark mode.
/// </summary>
public bool IsDarkMode { get; set; }
/// <summary> /// <summary>
/// The configuration data. /// The configuration data.
/// </summary> /// </summary>

View File

@ -7,6 +7,7 @@ public enum Event
// Common events: // Common events:
STATE_HAS_CHANGED, STATE_HAS_CHANGED,
CONFIGURATION_CHANGED, CONFIGURATION_CHANGED,
COLOR_THEME_CHANGED,
// Update events: // Update events:
USER_SEARCH_FOR_UPDATE, USER_SEARCH_FOR_UPDATE,

View File

@ -0,0 +1,12 @@
using AIStudio.Settings;
namespace AIStudio.Tools;
public static class MudThemeExtensions
{
public static Palette GetCurrentPalette(this MudTheme theme, SettingsManager settingsManager) => settingsManager.IsDarkMode switch
{
true => theme.PaletteDark,
false => theme.PaletteLight,
};
}

View File

@ -47,3 +47,11 @@
border-width: 2px; border-width: 2px;
border-color: var(--confidence-color) !important; border-color: var(--confidence-color) !important;
} }
:root {
--custom-icon-color: #000000;
}
.custom-icon-color > a > svg {
color: var(--custom-icon-color) !important;
}

View File

@ -1,5 +1,6 @@
# v0.9.11, build 186 # v0.9.11, build 186
- Added an option to enforce a minimum confidence level throughout the entire app. - Added an option to enforce a minimum confidence level throughout the entire app.
- Added options to enforce minimum confidence levels for each assistant individually. - Added options to enforce minimum confidence levels for each assistant individually.
- Added color themes (dark & light) to the app settings. The theme can be synchronized with the system theme.
- Added a tooltip to the confidence card button. - Added a tooltip to the confidence card button.
- Renamed the `Providers` enum to `LLMProviders` for better clarity. Renamed also all dependent variables and methods. - Renamed the `Providers` enum to `LLMProviders` for better clarity. Renamed also all dependent variables and methods.

BIN
media/Startup Icon.psd (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

BIN
runtime/ui/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -16,9 +16,15 @@
img { img {
display: block; display: block;
} }
@media (prefers-color-scheme: dark) {
html, body {
background-color: #1a1a1a;
}
}
</style> </style>
</head> </head>
<body> <body>
<img src="icon.jpg" width="512px" height="512px" alt="The app logo"> <img src="icon.png" width="512" height="512" alt="The app logo">
</body> </body>
</html> </html>