From 628be073b8be98a64f50a3794b22e1b88100e06e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 9 Aug 2026 19:47:46 +0200 Subject: [PATCH] Added an assistant category block which derives its heading from the visible assistants --- .../Components/AssistantBlock.razor.cs | 13 +++- .../Components/AssistantCategoryBlock.razor | 11 +++ .../AssistantCategoryBlock.razor.cs | 70 +++++++++++++++++++ .../Components/IAssistantCategoryMember.cs | 16 +++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 app/MindWork AI Studio/Components/AssistantCategoryBlock.razor create mode 100644 app/MindWork AI Studio/Components/AssistantCategoryBlock.razor.cs create mode 100644 app/MindWork AI Studio/Components/IAssistantCategoryMember.cs diff --git a/app/MindWork AI Studio/Components/AssistantBlock.razor.cs b/app/MindWork AI Studio/Components/AssistantBlock.razor.cs index ff639a0c..4486ae6c 100644 --- a/app/MindWork AI Studio/Components/AssistantBlock.razor.cs +++ b/app/MindWork AI Studio/Components/AssistantBlock.razor.cs @@ -9,7 +9,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Components; -public partial class AssistantBlock : MSGComponentBase where TSettings : IComponent +public partial class AssistantBlock : MSGComponentBase, IAssistantCategoryMember where TSettings : IComponent { /// /// Describes the assistant session indicator shown on top of the assistant icon. @@ -58,6 +58,12 @@ public partial class AssistantBlock : MSGComponentBase where TSetting [Parameter] public PreviewFeatures RequiredPreviewFeature { get; set; } = PreviewFeatures.NONE; + /// + /// Gets or sets the assistant category this block belongs to, if any. + /// + [CascadingParameter] + public AssistantCategoryBlock? Category { get; set; } + [Inject] private MudTheme ColorTheme { get; init; } = null!; @@ -88,7 +94,8 @@ public partial class AssistantBlock : 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); + /// + 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 : 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 : MSGComponentBase where TSetting protected override void DisposeResources() { this.MediaTranscriptionService.StateChanged -= this.OnMediaImportStateChanged; + this.Category?.UnregisterAssistant(this); base.DisposeResources(); } diff --git a/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor b/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor new file mode 100644 index 00000000..f6002b92 --- /dev/null +++ b/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor @@ -0,0 +1,11 @@ +@if (this.HasVisibleAssistant) +{ + + @this.Title + +} + + + @this.ChildContent + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor.cs b/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor.cs new file mode 100644 index 00000000..a204bb66 --- /dev/null +++ b/app/MindWork AI Studio/Components/AssistantCategoryBlock.razor.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Components; + +/// +/// Renders one category of assistants together with its heading. +/// +/// +/// 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. +/// +public partial class AssistantCategoryBlock : ComponentBase +{ + private readonly HashSet members = []; + + /// + /// The heading of this category. + /// + [Parameter] + public string Title { get; set; } = string.Empty; + + /// + /// The CSS classes used for the heading. + /// + [Parameter] + public string HeaderClass { get; set; } = "mb-2 mr-3 mt-6"; + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + /// + /// Adds an assistant block to this category. + /// + /// + /// 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. + /// + /// The assistant block which belongs to this category. + internal void RegisterAssistant(IAssistantCategoryMember member) + { + if (this.members.Add(member)) + this.StateHasChanged(); + } + + /// + /// Removes an assistant block from this category. + /// + /// The assistant block which no longer belongs to this category. + internal void UnregisterAssistant(IAssistantCategoryMember member) => this.members.Remove(member); + + /// + /// Gets whether at least one assistant of this category is visible right now. + /// + /// + /// We evaluate this live instead of caching it. That way, changes to the configuration take + /// effect as soon as the assistants page renders again. + /// + private bool HasVisibleAssistant => this.members.Any(member => member.IsVisible); + + /// + /// Gets the CSS classes used for the assistant stack. + /// + /// + /// 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. + /// + private string StackClass => this.HasVisibleAssistant ? "mb-3" : string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/IAssistantCategoryMember.cs b/app/MindWork AI Studio/Components/IAssistantCategoryMember.cs new file mode 100644 index 00000000..f4dd3033 --- /dev/null +++ b/app/MindWork AI Studio/Components/IAssistantCategoryMember.cs @@ -0,0 +1,16 @@ +namespace AIStudio.Components; + +/// +/// Represents an assistant block which belongs to an assistant category. +/// +/// +/// 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. +/// +public interface IAssistantCategoryMember +{ + /// + /// Gets whether the assistant is visible right now. + /// + bool IsVisible { get; } +} \ No newline at end of file