Info page: configuration details now refresh live (#712)
Some checks are pending
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer 2026-03-23 13:57:26 +01:00 committed by GitHub
parent e416552467
commit 37d026ea6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 52 additions and 14 deletions

View File

@ -100,7 +100,7 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus
Event.PLUGINS_RELOADED, Event.PLUGINS_RELOADED,
}; };
this.MessageBus.ApplyFilters(this, filterComponents, eventsList.ToArray()); this.MessageBus.ApplyFilters(this, filterComponents, eventsList.ToHashSet());
} }
protected virtual void DisposeResources() protected virtual void DisposeResources()

View File

@ -1,6 +1,5 @@
@attribute [Route(Routes.ABOUT)] @attribute [Route(Routes.ABOUT)]
@using AIStudio.Tools.PluginSystem @using AIStudio.Tools.PluginSystem
@using AIStudio.Tools.Services
@inherits MSGComponentBase @inherits MSGComponentBase
<div class="inner-scrolling-context"> <div class="inner-scrolling-context">
@ -85,7 +84,7 @@
@T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.") @T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.")
</MudText> </MudText>
<MudCollapse Expanded="@this.showEnterpriseConfigDetails"> <MudCollapse Expanded="@this.showEnterpriseConfigDetails">
@foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive)) @foreach (var env in this.enterpriseEnvironments.Where(e => e.IsActive))
{ {
<ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.HourglassBottom" <ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.HourglassBottom"
HeaderText="@T("Waiting for the configuration plugin...")" HeaderText="@T("Waiting for the configuration plugin...")"
@ -123,7 +122,7 @@
</MudText> </MudText>
} }
<MudCollapse Expanded="@this.showEnterpriseConfigDetails"> <MudCollapse Expanded="@this.showEnterpriseConfigDetails">
@foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive)) @foreach (var env in this.enterpriseEnvironments.Where(e => e.IsActive))
{ {
var matchingPlugin = this.FindManagedConfigurationPlugin(env.ConfigurationId); var matchingPlugin = this.FindManagedConfigurationPlugin(env.ConfigurationId);
if (matchingPlugin is null) if (matchingPlugin is null)

View File

@ -76,13 +76,15 @@ public partial class Information : MSGComponentBase
.OfType<IAvailablePlugin>() .OfType<IAvailablePlugin>()
.ToList(); .ToList();
private List<EnterpriseEnvironment> enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
private sealed record DatabaseDisplayInfo(string Label, string Value); private sealed record DatabaseDisplayInfo(string Label, string Value);
private readonly List<DatabaseDisplayInfo> databaseDisplayInfo = new(); private readonly List<DatabaseDisplayInfo> databaseDisplayInfo = new();
private static bool HasAnyActiveEnvironment => EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Any(e => e.IsActive); private bool HasAnyActiveEnvironment => this.enterpriseEnvironments.Any(e => e.IsActive);
private bool HasAnyLoadedEnterpriseConfigurationPlugin => EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS private bool HasAnyLoadedEnterpriseConfigurationPlugin => this.enterpriseEnvironments
.Where(e => e.IsActive) .Where(e => e.IsActive)
.Any(env => this.FindManagedConfigurationPlugin(env.ConfigurationId) is not null); .Any(env => this.FindManagedConfigurationPlugin(env.ConfigurationId) is not null);
@ -94,7 +96,7 @@ public partial class Information : MSGComponentBase
{ {
get get
{ {
return HasAnyActiveEnvironment switch return this.HasAnyActiveEnvironment switch
{ {
// Case 1: No enterprise config and no plugin - no details available // Case 1: No enterprise config and no plugin - no details available
false when this.configPlugins.Count == 0 => false, false when this.configPlugins.Count == 0 => false,
@ -115,8 +117,11 @@ public partial class Information : MSGComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.ApplyFilters([], [ Event.ENTERPRISE_ENVIRONMENTS_CHANGED ]);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
this.RefreshEnterpriseConfigurationState();
this.osLanguage = await this.RustService.ReadUserLanguage(); this.osLanguage = await this.RustService.ReadUserLanguage();
this.logPaths = await this.RustService.GetLogPaths(); this.logPaths = await this.RustService.GetLogPaths();
@ -139,10 +144,8 @@ public partial class Information : MSGComponentBase
switch (triggeredEvent) switch (triggeredEvent)
{ {
case Event.PLUGINS_RELOADED: case Event.PLUGINS_RELOADED:
this.configPlugins = PluginFactory.AvailablePlugins case Event.ENTERPRISE_ENVIRONMENTS_CHANGED:
.Where(x => x.Type is PluginType.CONFIGURATION) this.RefreshEnterpriseConfigurationState();
.OfType<IAvailablePlugin>()
.ToList();
await this.InvokeAsync(this.StateHasChanged); await this.InvokeAsync(this.StateHasChanged);
break; break;
} }
@ -152,6 +155,16 @@ public partial class Information : MSGComponentBase
#endregion #endregion
private void RefreshEnterpriseConfigurationState()
{
this.configPlugins = PluginFactory.AvailablePlugins
.Where(x => x.Type is PluginType.CONFIGURATION)
.OfType<IAvailablePlugin>()
.ToList();
this.enterpriseEnvironments = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.ToList();
}
private async Task DeterminePandocVersion() private async Task DeterminePandocVersion()
{ {
this.pandocInstallation = await Pandoc.CheckAvailabilityAsync(this.RustService, false); this.pandocInstallation = await Pandoc.CheckAvailabilityAsync(this.RustService, false);

View File

@ -11,6 +11,7 @@ public enum Event
STARTUP_PLUGIN_SYSTEM, STARTUP_PLUGIN_SYSTEM,
STARTUP_COMPLETED, STARTUP_COMPLETED,
STARTUP_ENTERPRISE_ENVIRONMENT, STARTUP_ENTERPRISE_ENVIRONMENT,
ENTERPRISE_ENVIRONMENTS_CHANGED,
PLUGINS_RELOADED, PLUGINS_RELOADED,
SHOW_ERROR, SHOW_ERROR,
SHOW_WARNING, SHOW_WARNING,

View File

@ -33,10 +33,10 @@ public sealed class MessageBus
/// <param name="receiver">That's you, the receiver.</param> /// <param name="receiver">That's you, the receiver.</param>
/// <param name="filterComponents">A list of components for which you want to receive messages. Use an empty list to receive messages from all components.</param> /// <param name="filterComponents">A list of components for which you want to receive messages. Use an empty list to receive messages from all components.</param>
/// <param name="events">A list of events for which you want to receive messages.</param> /// <param name="events">A list of events for which you want to receive messages.</param>
public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] filterComponents, Event[] events) public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] filterComponents, HashSet<Event> events)
{ {
this.componentFilters[receiver] = filterComponents; this.componentFilters[receiver] = filterComponents;
this.componentEvents[receiver] = events; this.componentEvents[receiver] = events.ToArray();
} }
public void RegisterComponent(IMessageBusReceiver receiver) public void RegisterComponent(IMessageBusReceiver receiver)

View File

@ -11,6 +11,6 @@ public static class MessageBusExtensions
public static void ApplyFilters(this IMessageBusReceiver component, ComponentBase[] components, Event[] events) public static void ApplyFilters(this IMessageBusReceiver component, ComponentBase[] components, Event[] events)
{ {
MessageBus.INSTANCE.ApplyFilters(component, components, events); MessageBus.INSTANCE.ApplyFilters(component, components, events.ToHashSet());
} }
} }

View File

@ -8,6 +8,8 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
public static bool HasValidEnterpriseSnapshot { get; private set; } public static bool HasValidEnterpriseSnapshot { get; private set; }
private readonly record struct EnterpriseEnvironmentSnapshot(Guid ConfigurationId, string ConfigurationServerUrl, string? ETag);
#if DEBUG #if DEBUG
private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(6); private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(6);
#else #else
@ -36,6 +38,7 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
{ {
logger.LogInformation("Start updating of the enterprise environment."); logger.LogInformation("Start updating of the enterprise environment.");
HasValidEnterpriseSnapshot = false; HasValidEnterpriseSnapshot = false;
var previousSnapshot = BuildNormalizedSnapshot(CURRENT_ENVIRONMENTS);
// //
// Step 1: Fetch all active configurations. // Step 1: Fetch all active configurations.
@ -165,12 +168,33 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
if (effectiveEnvironments.Count == 0) if (effectiveEnvironments.Count == 0)
logger.LogInformation("AI Studio runs without any enterprise configurations."); logger.LogInformation("AI Studio runs without any enterprise configurations.");
var effectiveSnapshot = BuildNormalizedSnapshot(effectiveEnvironments);
CURRENT_ENVIRONMENTS = effectiveEnvironments; CURRENT_ENVIRONMENTS = effectiveEnvironments;
HasValidEnterpriseSnapshot = true; HasValidEnterpriseSnapshot = true;
if (!previousSnapshot.SequenceEqual(effectiveSnapshot))
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.ENTERPRISE_ENVIRONMENTS_CHANGED);
} }
catch (Exception e) catch (Exception e)
{ {
logger.LogError(e, "An error occurred while updating the enterprise environment."); logger.LogError(e, "An error occurred while updating the enterprise environment.");
} }
} }
private static List<EnterpriseEnvironmentSnapshot> BuildNormalizedSnapshot(IEnumerable<EnterpriseEnvironment> environments)
{
return environments
.Where(environment => environment.IsActive)
.Select(environment => new EnterpriseEnvironmentSnapshot(
environment.ConfigurationId,
NormalizeServerUrl(environment.ConfigurationServerUrl),
environment.ETag?.ToString()))
.OrderBy(environment => environment.ConfigurationId)
.ToList();
}
private static string NormalizeServerUrl(string serverUrl)
{
return serverUrl.Trim().TrimEnd('/');
}
} }

View File

@ -10,6 +10,7 @@
- Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile. - Improved the profile selection for assistants and the chat. You can now explicitly choose between the app default profile, no profile, or a specific profile.
- Improved the performance by caching the OS language detection and requesting the user language only once per app start. - Improved the performance by caching the OS language detection and requesting the user language only once per app start.
- Improved the chat performance by reducing unnecessary UI updates, making chats smoother and more responsive, especially in longer conversations. - Improved the chat performance by reducing unnecessary UI updates, making chats smoother and more responsive, especially in longer conversations.
- Improved the information page so enterprise configuration details now refresh live when your organization's configuration changes.
- Improved the workspace loading experience: when opening the chat for the first time, your workspaces now appear faster and load step by step in the background, with placeholder rows so the app feels responsive right away. - Improved the workspace loading experience: when opening the chat for the first time, your workspaces now appear faster and load step by step in the background, with placeholder rows so the app feels responsive right away.
- Improved the reliability of the global voice recording shortcut so it stays available more consistently. - Improved the reliability of the global voice recording shortcut so it stays available more consistently.
- Improved the user-language logging by limiting language detection logs to a single entry per app start. - Improved the user-language logging by limiting language detection logs to a single entry per app start.