From 64e91ff4ffd70e6252d46c9b3a4056502818a0e3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 21 Jun 2026 15:59:23 +0200 Subject: [PATCH] Added support for organization-managed chat defaults (#817) --- .../Components/ChatComponent.razor.cs | 37 ++++++++++++++++--- .../Components/ChatTemplateSelection.razor.cs | 22 +++++++++++ .../Components/ProfileSelection.razor.cs | 22 +++++++++++ .../Components/ProviderSelection.razor.cs | 22 +++++++++++ .../Dialogs/Settings/SettingsDialogBase.cs | 3 ++ .../Dialogs/Settings/SettingsDialogChat.razor | 8 ++-- .../Plugins/configuration/plugin.lua | 26 +++++++++++++ .../Settings/DataModel/Data.cs | 2 +- .../Settings/DataModel/DataChat.cs | 19 +++++++--- .../Tools/PluginSystem/PluginConfiguration.cs | 6 +++ .../PluginSystem/PluginFactory.Loading.cs | 13 +++++++ .../wwwroot/changelog/v26.6.2.md | 2 + 12 files changed, 167 insertions(+), 15 deletions(-) diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 07be6277..8b3c6251 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -503,6 +503,36 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable this.currentChatTemplate = this.SettingsManager.GetChatTemplateById(this.currentChatTemplate.Id); } + private async Task RefreshChatSelectionsAfterConfigurationChange() + { + var previousProvider = this.Provider; + var previousChatTemplate = this.currentChatTemplate; + var chatProviderId = this.ChatThread?.SelectedProvider; + + this.Provider = this.SettingsManager.GetChatProviderForLoadedChat(chatProviderId); + if (this.Provider != previousProvider) + await this.ProviderChanged.InvokeAsync(this.Provider); + + if (this.ChatThread is null) + { + this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT); + this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT); + } + else + { + this.currentProfile = string.IsNullOrWhiteSpace(this.ChatThread.SelectedProfile) + ? this.SettingsManager.GetProfileById(this.currentProfile.Id) + : this.SettingsManager.GetProfileById(this.ChatThread.SelectedProfile); + + this.currentChatTemplate = string.IsNullOrWhiteSpace(this.ChatThread.SelectedChatTemplate) + ? this.SettingsManager.GetChatTemplateById(this.currentChatTemplate.Id) + : this.SettingsManager.GetChatTemplateById(this.ChatThread.SelectedChatTemplate); + } + + if (!this.ComposerState.HasUserDraft && previousChatTemplate != this.currentChatTemplate) + this.ComposerState.ApplyTemplate(this.currentChatTemplate); + } + private IReadOnlyList GetAgentSelectedDataSources() { if (this.ChatThread is null) @@ -1082,11 +1112,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable break; case Event.CONFIGURATION_CHANGED: - var previousChatTemplate = this.currentChatTemplate; - this.RefreshCurrentProfileAndChatTemplate(); - if (!this.ComposerState.HasUserDraft && previousChatTemplate != this.currentChatTemplate) - this.ComposerState.ApplyTemplate(this.currentChatTemplate); - + case Event.PLUGINS_RELOADED: + await this.RefreshChatSelectionsAfterConfigurationChange(); this.StateHasChanged(); break; diff --git a/app/MindWork AI Studio/Components/ChatTemplateSelection.razor.cs b/app/MindWork AI Studio/Components/ChatTemplateSelection.razor.cs index bf3eaa99..25bbdd1c 100644 --- a/app/MindWork AI Studio/Components/ChatTemplateSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ChatTemplateSelection.razor.cs @@ -32,6 +32,16 @@ public partial class ChatTemplateSelection : MSGComponentBase private string MarginClass => $"{this.MarginLeft} {this.MarginRight}"; + #region Overrides of ComponentBase + + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); + await base.OnInitializedAsync(); + } + + #endregion + private string ChatTemplateIcon(ChatTemplate chatTemplate) { if (chatTemplate.IsEnterpriseConfiguration) @@ -61,4 +71,16 @@ public partial class ChatTemplateSelection : MSGComponentBase }; await this.DialogService.ShowAsync(T("Open Chat Template Options"), dialogParameters, DialogOptions.FULLSCREEN); } + + #region Overrides of MSGComponentBase + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) + this.StateHasChanged(); + + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ProfileSelection.razor.cs b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs index 70747707..6c92aecc 100644 --- a/app/MindWork AI Studio/Components/ProfileSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs @@ -37,6 +37,16 @@ public partial class ProfileSelection : MSGComponentBase private string ToolTipText => this.Disabled ? this.DisabledText : this.defaultToolTipText; private string MarginClass => $"{this.MarginLeft} {this.MarginRight}"; + + #region Overrides of ComponentBase + + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); + await base.OnInitializedAsync(); + } + + #endregion private string ProfileIcon(Profile profile) { @@ -57,4 +67,16 @@ public partial class ProfileSelection : MSGComponentBase var dialogParameters = new DialogParameters(); await this.DialogService.ShowAsync(T("Open Profile Options"), dialogParameters, DialogOptions.FULLSCREEN); } + + #region Overrides of MSGComponentBase + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) + this.StateHasChanged(); + + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs index 809ed089..74bd75c9 100644 --- a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs @@ -25,6 +25,16 @@ public partial class ProviderSelection : MSGComponentBase [Inject] private ILogger Logger { get; init; } = null!; + + #region Overrides of ComponentBase + + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); + await base.OnInitializedAsync(); + } + + #endregion private async Task SelectionChanged(AIStudio.Settings.Provider provider) { @@ -62,4 +72,16 @@ public partial class ProviderSelection : MSGComponentBase break; } } + + #region Overrides of MSGComponentBase + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + if (triggeredEvent is Event.CONFIGURATION_CHANGED or Event.PLUGINS_RELOADED) + this.StateHasChanged(); + + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs index 3fc5f45e..0b235fd2 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs @@ -65,6 +65,9 @@ public abstract class SettingsDialogBase : MSGComponentBase switch (triggeredEvent) { case Event.CONFIGURATION_CHANGED: + case Event.PLUGINS_RELOADED: + this.UpdateProviders(); + this.UpdateEmbeddingProviders(); this.StateHasChanged(); break; } diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor index 1dd6b9d7..348f7a53 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor @@ -16,10 +16,10 @@ - - - - + + + + @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 46f05641..aa4241c7 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -238,6 +238,32 @@ CONFIG["SETTINGS"] = {} -- Please note: using an empty string ("") will lock the preselected profile selection, even though no valid preselected profile is found. -- CONFIG["SETTINGS"]["DataApp.PreselectedProfile"] = "00000000-0000-0000-0000-000000000000" +-- Configure chat-specific preselected options. +-- This must be enabled for the chat-specific provider, profile, and chat template to take effect. +-- CONFIG["SETTINGS"]["DataChat.PreselectOptions"] = true +-- +-- Configure the preselected provider for chats. +-- It must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"]. +-- CONFIG["SETTINGS"]["DataChat.PreselectedProvider"] = "00000000-0000-0000-0000-000000000000" +-- +-- Configure the preselected profile for chats. +-- It must be one of the profile IDs defined in CONFIG["PROFILES"]. +-- Please note: using an empty string ("") means chats will use the app default profile. +-- Please note: using "00000000-0000-0000-0000-000000000000" means chats will use no profile. +-- CONFIG["SETTINGS"]["DataChat.PreselectedProfile"] = "00000000-0000-0000-0000-000000000000" +-- +-- Configure the preselected chat template for chats. +-- It must be one of the chat template IDs defined in CONFIG["CHAT_TEMPLATES"]. +-- Please note: using an empty string ("") or "00000000-0000-0000-0000-000000000000" means chats will use no chat template. +-- CONFIG["SETTINGS"]["DataChat.PreselectedChatTemplate"] = "00000000-0000-0000-0000-000000000000" +-- +-- Allow users to change any configured chat default locally. +-- Allowed values are: true, false +-- CONFIG["SETTINGS"]["DataChat.PreselectOptions.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedProvider.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedProfile.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedChatTemplate.AllowUserOverride"] = true + -- Configure the transcription provider for voice-to-text functionality. -- It must be one of the transcription provider IDs defined in CONFIG["TRANSCRIPTION_PROVIDERS"]. -- Without a selected transcription provider, dictation and transcription features will be disabled. diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index 3a232017..07f9f9e2 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -105,7 +105,7 @@ public sealed class Data public DataApp App { get; init; } = new(x => x.App); - public DataChat Chat { get; init; } = new(); + public DataChat Chat { get; init; } = new(x => x.Chat); public DataWorkspace Workspace { get; init; } = new(); diff --git a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs index 147bb7ac..f2a7ea37 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs @@ -1,7 +1,16 @@ +using System.Linq.Expressions; + namespace AIStudio.Settings.DataModel; -public sealed class DataChat +public sealed class DataChat(Expression>? configSelection = null) { + /// + /// The default constructor for the JSON deserializer. + /// + public DataChat() : this(null) + { + } + /// /// Shortcuts to send the input to the AI. /// @@ -25,22 +34,22 @@ public sealed class DataChat /// /// Preselect any chat options? /// - public bool PreselectOptions { get; set; } + public bool PreselectOptions { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectOptions, false); /// /// Should we preselect a provider for the chat? /// - public string PreselectedProvider { get; set; } = string.Empty; + public string PreselectedProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedProvider, string.Empty); /// /// Preselect a profile? /// - public string PreselectedProfile { get; set; } = string.Empty; + public string PreselectedProfile { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedProfile, string.Empty); /// /// Preselect a chat template? /// - public string PreselectedChatTemplate { get; set; } = string.Empty; + public string PreselectedChatTemplate { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedChatTemplate, string.Empty); /// /// Should we preselect data sources options for a created chat? diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 3677c252..92634f7b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -238,6 +238,12 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: preselected profile? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProfile, Guid.Empty, this.Id, settingsTable, dryRun); + // Config: preselected chat options? + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectOptions, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedProfile, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedChatTemplate, this.Id, settingsTable, dryRun); + // Config: transcription provider? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UseTranscriptionProvider, Guid.Empty, this.Id, settingsTable, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index c4d488c5..3f74c556 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -201,6 +201,19 @@ public static partial class PluginFactory // Check for a preselected profile: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.PreselectedProfile, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + + // Check for preselected chat options: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectOptions, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedProvider, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedProfile, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedChatTemplate, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; // Check for the update interval: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateInterval, AVAILABLE_PLUGINS)) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md index b606e448..8dde7dcc 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md @@ -1,8 +1,10 @@ # v26.6.2, build 242 (2026-06-xx xx:xx UTC) - Added a read-only view for organization-managed profiles and chat templates, so users can inspect the content while the organization remains in control of changes. +- Added support for organization-managed chat defaults. Configuration plugins can now preselect the chat provider, profile, and chat template, either as locked values or editable defaults. - Added support for organization-managed introduction texts on the home page. Configuration plugins can now add custom Markdown introductions and hide the built-in introduction. - Added support for organization-managed provider confidence settings. Configuration plugins can now set confidence presets, custom confidence schemes, and an app-wide minimum confidence level. - Added support for organization-trusted providers in data source security checks. Configuration plugins can now mark specific provider instances as trusted for data source usage and local embedding warnings. - Changed provider confidence settings to appear in their own settings panel, because they apply to LLM, embedding, and transcription providers. +- Fixed chat provider, profile, and template selections not updating live after configuration plugins were changed. - Fixed organization-managed chat templates not showing the correct icon in the chat template selection menu. - Fixed self-hosted provider API keys sometimes being stored under a localized name. AI Studio now uses a stable key name, keeps correct entries working, and automatically migrates known localized entries for LLM, transcription, and embedding providers. Organizations using configuration plugins do not need to change their plugins; affected users who still see an invalid API key warning should open the provider, transcription, or embedding settings and update the API key once. \ No newline at end of file