Merge branch 'main' into fix/Darkmode-switch

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-09-22 21:32:06 +02:00
commit c9d3760435
1036 files changed
+342361 -8176

No files matched your search

+32 -9
View File
@@ -25,10 +25,19 @@
<MudSpacer/>
<MudStack AlignItems="AlignItems.Center">
<MudToolBar WrapContent="true">
<VoiceRecorder />
</MudToolBar>
@* The bottom area carries the gap to the window edge once, for whichever of its items are shown: *@
<MudStack Spacing="0" Class="pb-2">
@if (this.showEmbeddingStatusIcon)
{
<MudNavMenu>
<MudTooltip Text="@this.EmbeddingNavigationTooltip" Placement="Placement.Right">
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color">
@T("Data sync")
</MudNavLink>
</MudTooltip>
</MudNavMenu>
}
<VoiceRecorder />
</MudStack>
</MudDrawer>
</MudDrawerContainer>
@@ -53,11 +62,25 @@
</MudNavMenu>
<MudSpacer/>
<MudStack AlignItems="AlignItems.Center">
<MudToolBar WrapContent="true">
<VoiceRecorder />
</MudToolBar>
@* The bottom area carries the gap to the window edge once, for whichever of its items are shown: *@
<MudStack Spacing="0" Class="pb-2">
@if (this.showEmbeddingStatusIcon)
{
<MudNavMenu>
@if (this.SettingsManager.ConfigurationData.App.NavigationBehavior is NavBehavior.NEVER_EXPAND_USE_TOOLTIPS)
{
<MudTooltip Text="@this.EmbeddingNavigationTooltip" Placement="Placement.Right">
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color"/>
</MudTooltip>
}
else
{
<MudNavLink Href="@this.embeddingItem.Path" Match="@(this.embeddingItem.MatchAll ? NavLinkMatch.All : NavLinkMatch.Prefix)" Icon="@this.embeddingItem.Icon" Style="@this.embeddingItem.SetColorStyle(this.SettingsManager)" Class="custom-icon-color"/>
}
</MudNavMenu>
}
<VoiceRecorder />
</MudStack>
</MudPaper>
}
+113 -11
View File
@@ -5,6 +5,7 @@ using AIStudio.Tools.AIJobs;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Security;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
@@ -53,6 +54,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
[Inject]
private DataSourceEmbeddingService DataSourceEmbeddingService { get; init; } = null!;
[Inject]
private CircuitStateService CircuitState { get; init; } = null!;
private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage;
@@ -71,8 +78,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
private bool startupCompleted;
private bool settingsWriteProtectionWarningShown;
private readonly SemaphoreSlim mandatoryInfoDialogSemaphore = new(1, 1);
private readonly SemaphoreSlim promptInjectionDialogSemaphore = new(1, 1);
private DataSourceEmbeddingOverview embeddingOverview = new(DataSourceEmbeddingState.COMPLETED, 0, 0, 0);
private IReadOnlyCollection<NavBarItem> navItems = [];
private NavBarItem embeddingItem = new (string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, false);
private bool showEmbeddingStatusIcon;
#region Overrides of ComponentBase
@@ -106,15 +117,17 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
// Ensure that all settings are loaded:
await this.SettingsManager.LoadSettings();
await this.DataSourceEmbeddingService.QueueAllInternalDataSourcesIfAutomaticRefreshAsync();
// Register this component with the message bus:
this.MessageBus.RegisterComponent(this);
this.MessageBus.RegisterComponent(this, this.CircuitState);
this.MessageBus.ApplyFilters(this, [],
[
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.SHOW_INFO, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.SHOW_INFO, Event.SHOW_PROMPT_INJECTION_ALERT, Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED,
Event.INSTALL_UPDATE, Event.STARTUP_COMPLETED, Event.AI_JOB_CHANGED, Event.AI_JOB_FINISHED,
Event.CHAT_GENERATION_CHANGED, Event.ASSISTANT_SESSION_CHANGED, Event.ASSISTANT_SESSION_FINISHED,
Event.CHAT_GENERATION_CHANGED, Event.RAG_EMBEDDING_STATUS_CHANGED,Event.ASSISTANT_SESSION_CHANGED,
Event.ASSISTANT_SESSION_FINISHED,
]);
// Set the snackbar for the update service:
@@ -134,6 +147,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
await this.themeProvider.WatchSystemDarkModeAsync(this.SystemeThemeChanged);
await this.UpdateThemeConfiguration();
this.LoadNavItems();
this.LoadEmbeddingItem();
await base.OnInitializedAsync();
}
@@ -230,9 +244,10 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
await this.UpdateThemeConfiguration();
this.LoadNavItems();
this.LoadEmbeddingItem();
this.StateHasChanged();
if (this.startupCompleted)
_ = this.EnsureMandatoryInfosAcceptedAsync();
this.EnsureMandatoryInfosAcceptedAsync().Observe($"{nameof(MainLayout)}: mandatory infos after a configuration change");
break;
case Event.COLOR_THEME_CHANGED:
@@ -254,6 +269,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
break;
case Event.SHOW_PROMPT_INJECTION_ALERT:
if (data is PromptInjectionAlertMessage promptInjectionAlert)
await this.ShowPromptInjectionAlertAsync(promptInjectionAlert);
break;
case Event.SHOW_ERROR:
if (data is DataErrorMessage error)
error.Show(this.Snackbar);
@@ -273,7 +294,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
break;
case Event.STARTUP_PLUGIN_SYSTEM:
_ = Task.Run(async () =>
Task.Run(async () =>
{
// Set up the plugin system:
if (PluginFactory.Setup())
@@ -284,8 +305,10 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
//
// Check if there is an enterprise configuration plugin to download:
//
// Every deferred environment matters here: each one is a configuration
// to download, so this is the one place which uses all of them.
var enterpriseEnvironments = this.MessageBus
.CheckDeferredMessages<EnterpriseEnvironment>(Event.STARTUP_ENTERPRISE_ENVIRONMENT)
.TakeDeferredMessages<EnterpriseEnvironment>(Event.STARTUP_ENTERPRISE_ENVIRONMENT)
.Where(env => env != default)
.ToList();
@@ -326,7 +349,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
PluginFactory.SetUpHotReloading();
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_COMPLETED);
}
});
}).Observe($"{nameof(MainLayout)}: setting up the plugin system");
break;
case Event.PLUGINS_RELOADED:
@@ -334,20 +357,53 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
I18N.Init(this.Lang);
this.ShowSettingsWriteProtectionWarning();
this.LoadNavItems();
this.LoadEmbeddingItem();
await this.InvokeAsync(this.StateHasChanged);
if (this.startupCompleted)
_ = this.EnsureMandatoryInfosAcceptedAsync();
this.EnsureMandatoryInfosAcceptedAsync().Observe($"{nameof(MainLayout)}: mandatory infos after a plugin reload");
break;
case Event.STARTUP_COMPLETED:
this.startupCompleted = true;
_ = this.EnsureMandatoryInfosAcceptedAsync();
this.EnsureMandatoryInfosAcceptedAsync().Observe($"{nameof(MainLayout)}: mandatory infos after the startup");
break;
case Event.RAG_EMBEDDING_STATUS_CHANGED:
this.LoadNavItems();
this.LoadEmbeddingItem();
this.StateHasChanged();
break;
}
});
}
private async Task ShowPromptInjectionAlertAsync(PromptInjectionAlertMessage alert)
{
await this.promptInjectionDialogSemaphore.WaitAsync();
try
{
if (!this.SettingsManager.ConfigurationData.App.ShowPromptInjectionAlert)
return;
var dialogParameters = new DialogParameters<PromptInjectionAlertDialog>
{
{ x => x.Alert, alert },
};
var dialogReference = await this.DialogService.ShowAsync<PromptInjectionAlertDialog>(
T("Security notice"),
dialogParameters,
DialogOptions.FULLSCREEN);
await dialogReference.Result;
}
finally
{
this.promptInjectionDialogSemaphore.Release();
}
}
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
@@ -363,11 +419,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
/// <summary>Refreshes navigation activity colors when a media import changes state.</summary>
private void OnMediaImportStateChanged(MediaImportOwner owner)
{
_ = this.InvokeAsync(() =>
this.InvokeAsync(() =>
{
this.LoadNavItems();
this.StateHasChanged();
});
}).Observe($"{nameof(MainLayout)}: refreshing the navigation after a media import change");
}
private IEnumerable<NavBarItem> GetNavItems()
@@ -400,6 +456,52 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
yield return new(T("Settings"), Icons.Material.Filled.Settings, defaultLightColor, defaultDarkColor, Routes.SETTINGS, false);
}
private void LoadEmbeddingItem()
{
this.embeddingOverview = this.DataSourceEmbeddingService.GetOverview();
//
// The entry is shown whenever local RAG is available, in every state. Hiding it while
// nothing was running looked tidier, but a data source which was just added has no status
// yet: the service creates one when the run begins. The icon was therefore missing during
// the very moment the user was waiting for it. What the entry does communicate is its
// state, through the icon below.
//
// The preview feature is what gates it now. The route itself is not gated, so without this
// check, users who have no RAG at all would get a navigation entry for it.
//
this.showEmbeddingStatusIcon = PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager);
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
(string icon, string lightcolor, string darkcolor) embeddingIcon = this.embeddingOverview.State switch
{
DataSourceEmbeddingState.FAILED => (Icons.Material.Filled.Warning, palette.Error.Value, "#d32f2f"),
DataSourceEmbeddingState.QUEUED => (Icons.Material.Filled.Sync, palette.Info.Value, "#1976d2"),
DataSourceEmbeddingState.RUNNING => (Icons.Material.Filled.Sync, palette.Warning.Value, "#d29f00"),
// Nothing to do: the entry keeps the colors of its neighbors, so a permanently visible
// icon does not draw attention while there is nothing to attend to:
_ => (Icons.Material.Filled.CloudDone, palette.DarkLighten, palette.GrayLight),
};
this.embeddingItem = new NavBarItem(T("Embeddings"), embeddingIcon.icon, embeddingIcon.lightcolor, embeddingIcon.darkcolor, Routes.EMBEDDINGS, false);
}
private string EmbeddingNavigationTooltip => this.embeddingOverview.State switch
{
DataSourceEmbeddingState.QUEUED => T("Embeddings are waiting to be processed."),
DataSourceEmbeddingState.RUNNING => string.Format(
T("Embeddings are running: {0} of {1} files are indexed."),
this.embeddingOverview.IndexedFiles,
this.embeddingOverview.TotalFiles),
DataSourceEmbeddingState.FAILED => this.embeddingOverview.FailedFiles > 0
? string.Format(T("Some embeddings failed. {0} file(s) need attention."), this.embeddingOverview.FailedFiles)
: T("Some embeddings failed and need attention."),
// The entry is always visible, so its resting state needs words as well. An empty tooltip
// would leave the user guessing what the icon is there for:
_ => T("All data sources are up to date.")
};
private async Task ShowUpdateDialog()
{
if (!this.UpdatePolicy.AllowsInstallations)