Fixed empty assistant categories after hiding all their assistants (#902)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (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, nsis) (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,updater, appimage) (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,app,updater, dmg) (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, nsis) (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,updater, appimage) (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-08-09 20:00:38 +02:00 committed by GitHub
parent 6e143aafaa
commit a3cccb7c7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 142 additions and 111 deletions

View File

@ -9,7 +9,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
public partial class AssistantBlock<TSettings> : MSGComponentBase where TSettings : IComponent
public partial class AssistantBlock<TSettings> : MSGComponentBase, IAssistantCategoryMember where TSettings : IComponent
{
/// <summary>
/// Describes the assistant session indicator shown on top of the assistant icon.
@ -58,6 +58,12 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
[Parameter]
public PreviewFeatures RequiredPreviewFeature { get; set; } = PreviewFeatures.NONE;
/// <summary>
/// Gets or sets the assistant category this block belongs to, if any.
/// </summary>
[CascadingParameter]
public AssistantCategoryBlock? Category { get; set; }
[Inject]
private MudTheme ColorTheme { get; init; } = null!;
@ -88,7 +94,8 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
private string BlockStyle => $"border-width: 3px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;";
private bool IsVisible => this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Name, requiredPreviewFeature: this.RequiredPreviewFeature);
/// <inheritdoc />
public bool IsVisible => this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Name, requiredPreviewFeature: this.RequiredPreviewFeature);
private bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
@ -153,6 +160,7 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
protected override async Task OnInitializedAsync()
{
this.MediaTranscriptionService.StateChanged += this.OnMediaImportStateChanged;
this.Category?.RegisterAssistant(this);
await base.OnInitializedAsync();
}
@ -165,6 +173,7 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
protected override void DisposeResources()
{
this.MediaTranscriptionService.StateChanged -= this.OnMediaImportStateChanged;
this.Category?.UnregisterAssistant(this);
base.DisposeResources();
}

View File

@ -0,0 +1,11 @@
@if (this.HasVisibleAssistant)
{
<MudText Typo="Typo.h4" Class="@this.HeaderClass">
@this.Title
</MudText>
}
<CascadingValue Value="this" IsFixed="@true">
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="@this.StackClass">
@this.ChildContent
</MudStack>
</CascadingValue>

View File

@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
/// <summary>
/// Renders one category of assistants together with its heading.
/// </summary>
/// <remarks>
/// The heading is derived from the assistant blocks inside this category: it is rendered only when
/// at least one of them is visible. Thus, hiding assistants by configuration can never leave an
/// empty category heading behind.
/// </remarks>
public partial class AssistantCategoryBlock : ComponentBase
{
private readonly HashSet<IAssistantCategoryMember> members = [];
/// <summary>
/// The heading of this category.
/// </summary>
[Parameter]
public string Title { get; set; } = string.Empty;
/// <summary>
/// The CSS classes used for the heading.
/// </summary>
[Parameter]
public string HeaderClass { get; set; } = "mb-2 mr-3 mt-6";
[Parameter]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Adds an assistant block to this category.
/// </summary>
/// <remarks>
/// Assistant blocks call this while they initialize, i.e. after this category was rendered for
/// the first time. Hence, we have to render again to show the heading.
/// </remarks>
/// <param name="member">The assistant block which belongs to this category.</param>
internal void RegisterAssistant(IAssistantCategoryMember member)
{
if (this.members.Add(member))
this.StateHasChanged();
}
/// <summary>
/// Removes an assistant block from this category.
/// </summary>
/// <param name="member">The assistant block which no longer belongs to this category.</param>
internal void UnregisterAssistant(IAssistantCategoryMember member) => this.members.Remove(member);
/// <summary>
/// Gets whether at least one assistant of this category is visible right now.
/// </summary>
/// <remarks>
/// We evaluate this live instead of caching it. That way, changes to the configuration take
/// effect as soon as the assistants page renders again.
/// </remarks>
private bool HasVisibleAssistant => this.members.Any(member => member.IsVisible);
/// <summary>
/// Gets the CSS classes used for the assistant stack.
/// </summary>
/// <remarks>
/// The stack must be rendered even when no assistant is visible, because the assistant blocks
/// register themselves while rendering. Without any visible assistant, we drop the margin so
/// that a hidden category leaves no gap behind.
/// </remarks>
private string StackClass => this.HasVisibleAssistant ? "mb-3" : string.Empty;
}

View File

@ -0,0 +1,16 @@
namespace AIStudio.Components;
/// <summary>
/// Represents an assistant block which belongs to an assistant category.
/// </summary>
/// <remarks>
/// Assistant blocks are generic over their settings dialog. This interface gives the category block
/// access to their visibility without the need to know that type parameter.
/// </remarks>
public interface IAssistantCategoryMember
{
/// <summary>
/// Gets whether the assistant is visible right now.
/// </summary>
bool IsVisible { get; }
}

View File

@ -12,20 +12,7 @@
<InnerScrolling>
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("General",
(Components.TEXT_SUMMARIZER_ASSISTANT, PreviewFeatures.NONE),
(Components.TRANSLATION_ASSISTANT, PreviewFeatures.NONE),
(Components.GRAMMAR_SPELLING_ASSISTANT, PreviewFeatures.NONE),
(Components.REWRITE_ASSISTANT, PreviewFeatures.NONE),
(Components.PROMPT_OPTIMIZER_ASSISTANT, PreviewFeatures.NONE),
(Components.SYNONYMS_ASSISTANT, PreviewFeatures.NONE),
(Components.META_ASSISTANT, PreviewFeatures.PRE_META_ASSISTANT_V1)
))
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
@T("General")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("General")" HeaderClass="mb-2 mr-3">
<AssistantBlock TSettings="SettingsDialogTextSummarizer" Component="Components.TEXT_SUMMARIZER_ASSISTANT" Name="@T("Text Summarizer")" Description="@T("Use an LLM to summarize a given text.")" Icon="@Icons.Material.Filled.TextSnippet" Link="@Routes.ASSISTANT_SUMMARIZER"/>
<AssistantBlock TSettings="SettingsDialogTranslation" Component="Components.TRANSLATION_ASSISTANT" Name="@T("Translation")" Description="@T("Translate text into another language.")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_TRANSLATION"/>
<AssistantBlock TSettings="SettingsDialogGrammarSpelling" Component="Components.GRAMMAR_SPELLING_ASSISTANT" Name="@T("Grammar & Spelling")" Description="@T("Check grammar and spelling of a given text.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_GRAMMAR_SPELLING"/>
@ -33,15 +20,11 @@
<AssistantBlock TSettings="SettingsDialogPromptOptimizer" Component="Components.PROMPT_OPTIMIZER_ASSISTANT" Name="@T("Prompt Optimizer")" Description="@T("Optimize your prompt using a structured guideline.")" Icon="@Icons.Material.Filled.AutoFixHigh" Link="@Routes.ASSISTANT_PROMPT_OPTIMIZER"/>
<AssistantBlock TSettings="SettingsDialogSynonyms" Component="Components.SYNONYMS_ASSISTANT" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.META_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_META_ASSISTANT_V1" Name="@T("Assistant Builder")" Description="@T("Generate your own assistants.")" Icon="@Icons.Material.Filled.AutoMode" Link="@Routes.ASSISTANT_META_ASSISTANT"/>
</MudStack>
}
</AssistantCategoryBlock>
@if (this.AssistantPlugins.Count > 0)
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
@T("Installed Assistants")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("Installed Assistants")">
@foreach (var assistantPlugin in this.AssistantPlugins)
{
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
@ -66,25 +49,10 @@
</SecurityBadge>
</AssistantBlock>
}
</MudStack>
</AssistantCategoryBlock>
}
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Business",
(Components.EMAIL_ASSISTANT, PreviewFeatures.NONE),
(Components.DOCUMENT_ANALYSIS_ASSISTANT, PreviewFeatures.NONE),
(Components.MY_TASKS_ASSISTANT, PreviewFeatures.NONE),
(Components.AGENDA_ASSISTANT, PreviewFeatures.NONE),
(Components.JOB_POSTING_ASSISTANT, PreviewFeatures.NONE),
(Components.LEGAL_CHECK_ASSISTANT, PreviewFeatures.NONE),
(Components.ICON_FINDER_ASSISTANT, PreviewFeatures.NONE),
(Components.SLIDE_BUILDER_ASSISTANT, PreviewFeatures.NONE),
(Components.VISUAL_BRIEFING_ASSISTANT, Components.VISUAL_BRIEFING_ASSISTANT.RequiredPreviewFeature())
))
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
@T("Business")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("Business")">
<AssistantBlock TSettings="SettingsDialogWritingEMails" Component="Components.EMAIL_ASSISTANT" Name="@T("E-Mail")" Description="@T("Generate an e-mail for a given context.")" Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" Name="@T("Document Analysis")" Description="@T("Analyze a document regarding defined rules and extract key information.")" Icon="@Icons.Material.Filled.DocumentScanner" Link="@Routes.ASSISTANT_DOCUMENT_ANALYSIS"/>
<AssistantBlock TSettings="SettingsDialogMyTasks" Component="Components.MY_TASKS_ASSISTANT" Name="@T("My Tasks")" Description="@T("Analyze a text or an email for tasks you need to complete.")" Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
@ -94,48 +62,21 @@
<AssistantBlock TSettings="SettingsDialogIconFinder" Component="Components.ICON_FINDER_ASSISTANT" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
<AssistantBlock TSettings="SettingsDialogSlideBuilder" Component="Components.SLIDE_BUILDER_ASSISTANT" Name="@T("Slide Planner Assistant")" Description="@T("Develop slide content based on a given topic and content.")" Icon="@Icons.Material.Filled.Slideshow" Link="@Routes.ASSISTANT_SLIDE_BUILDER"/>
<AssistantBlock TSettings="SettingsDialogVisualBriefing" Component="Components.VISUAL_BRIEFING_ASSISTANT" RequiredPreviewFeature="Components.VISUAL_BRIEFING_ASSISTANT.RequiredPreviewFeature()" Name="@T("Visual Briefing Assistant")" Description="@T("Turn documents, data, images, audio, and video into an audience-ready interactive briefing.")" Icon="@Icons.Material.Filled.DashboardCustomize" Link="@Routes.ASSISTANT_VISUAL_BRIEFING" />
</MudStack>
}
</AssistantCategoryBlock>
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Learning",
(Components.BIAS_DAY_ASSISTANT, PreviewFeatures.NONE)
))
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
@T("Learning")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("Learning")">
<AssistantBlock TSettings="SettingsDialogAssistantBias" Component="Components.BIAS_DAY_ASSISTANT" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
</MudStack>
}
</AssistantCategoryBlock>
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Software Engineering",
(Components.CODING_ASSISTANT, PreviewFeatures.NONE),
(Components.ERI_ASSISTANT, PreviewFeatures.PRE_RAG_2024),
(Components.LOG_VIEWER_ASSISTANT, PreviewFeatures.NONE)
))
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
@T("Software Engineering")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("Software Engineering")">
<AssistantBlock TSettings="SettingsDialogCoding" Component="Components.CODING_ASSISTANT" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
<AssistantBlock TSettings="SettingsDialogERIServer" Component="Components.ERI_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_RAG_2024" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
</MudStack>
}
</AssistantCategoryBlock>
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("AI Studio Development",
(Components.I18N_ASSISTANT, PreviewFeatures.NONE)
))
{
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
@T("AI Studio Development")
</MudText>
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
<AssistantCategoryBlock Title="@T("AI Studio Development")">
<AssistantBlock TSettings="SettingsDialogI18N" Component="Components.I18N_ASSISTANT" Name="@T("Localization")" Description="@T("Translate AI Studio text content into other languages")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_AI_STUDIO_I18N"/>
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.LOG_VIEWER_ASSISTANT" Name="@T("Log Viewer")" Description="@T("View and filter AI Studio log files.")" Icon="@Icons.Material.Filled.Article" Link="@Routes.ASSISTANT_LOG_VIEWER"/>
</MudStack>
}
</AssistantCategoryBlock>
</InnerScrolling>
</div>

View File

@ -84,21 +84,4 @@ public static class AssistantVisibilityExtensions
return !isHidden;
}
/// <summary>
/// Checks if any assistant in a category should be visible.
/// </summary>
/// <param name="settingsManager">The settings manager to check configuration against.</param>
/// <param name="categoryName">The name of the assistant category (for logging purposes).</param>
/// <param name="assistants">The assistants in the category with their optional preview feature requirements.</param>
/// <returns>True if at least one assistant in the category should be visible, false otherwise.</returns>
public static bool IsAnyCategoryAssistantVisible(this SettingsManager settingsManager, string categoryName, params (Components Component, PreviewFeatures RequiredPreviewFeature)[] assistants)
{
foreach (var (component, requiredPreviewFeature) in assistants)
if (settingsManager.IsAssistantVisible(component, withLogging: false, requiredPreviewFeature: requiredPreviewFeature))
return true;
LOGGER.LogInformation("No assistants in category '{CategoryName}' are visible.", categoryName);
return false;
}
}

View File

@ -18,4 +18,5 @@
- Fixed withdrawing a configuration your organization deployed. A configuration that declared itself as locally managed stayed on the device even after the IT department stopped deploying it, and it kept every right of an organization configuration, such as approving assistant plugins. Where a configuration is stored now decides this instead of what the configuration says about itself, so withdrawing one always takes effect. This also applies to a device that was offline while the organization changed its policy: the withdrawal is applied when AI Studio starts again.
- Fixed preview features contributed by several configuration plugins at once. Only the most recent contribution was recognized as coming from your organization, so features enabled by another configuration looked as if you had switched them on yourself. Each configuration is now tracked separately, which lets your organization enable one preview feature company-wide and another one for a single department.
- Fixed which configuration wins when two configuration plugins collide, e.g. by claiming the same plugin ID, by managing the same setting, or by defining the same provider. Previously, this was down to chance, so a local configuration plugin could take over parts of the configuration your IT department deployed. Configurations from your organization now always win, and every ignored attempt is reported in the log.
- Fixed the assistant categories when your organization hides individual assistants. A category heading could stay visible above an empty area, and the Log Viewer could disappear together with the Localization assistant. Each heading now follows the assistants actually shown below it.
- Upgraded dependencies to their latest versions to improve security and stability.