diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index acfd2fce..06b6fb92 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -59,6 +59,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private DataSourceSelection? dataSourceSelectionComponent; private DataSourceOptions earlyDataSourceOptions = new(); + private DataSourceOptions lastAppliedStandardDataSourceOptions = new(); private Profile currentProfile = Profile.NO_PROFILE; private ChatTemplate currentChatTemplate = ChatTemplate.NO_CHAT_TEMPLATE; private bool hasUnsavedChanges; @@ -118,6 +119,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (!this.ComposerState.HasUserDraft && !this.ComposerState.HasComposerContent) this.ComposerState.ApplyTemplate(this.currentChatTemplate); + this.lastAppliedStandardDataSourceOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + var deferredInput = MessageBus.INSTANCE.CheckDeferredMessages(Event.SEND_TO_CHAT_INPUT).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(deferredInput)) this.ComposerState.SetUserInput(deferredInput); @@ -458,12 +461,42 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private void ApplyStandardDataSourceOptions() { var chatDefaultOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + this.lastAppliedStandardDataSourceOptions = chatDefaultOptions.CreateCopy(); this.earlyDataSourceOptions = chatDefaultOptions; if(this.ChatThread is not null) this.ChatThread.DataSourceOptions = chatDefaultOptions; this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(chatDefaultOptions); } + + private async Task ApplyUpdatedStandardDataSourceOptionsAfterConfigurationChange() + { + var updatedStandardOptions = this.SettingsManager.ConfigurationData.Chat.PreselectedDataSourceOptions.CreateCopy(); + var previousStandardOptions = this.lastAppliedStandardDataSourceOptions; + this.lastAppliedStandardDataSourceOptions = updatedStandardOptions.CreateCopy(); + + if (this.ChatThread is null) + { + this.earlyDataSourceOptions = updatedStandardOptions; + this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(updatedStandardOptions); + return; + } + + if (!DataSourceOptionsAreEqual(this.ChatThread.DataSourceOptions, previousStandardOptions)) + return; + + await this.SetCurrentDataSourceOptions(updatedStandardOptions); + this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(updatedStandardOptions, this.ChatThread.AISelectedDataSources); + await this.ChatThreadChanged.InvokeAsync(this.ChatThread); + } + + private static bool DataSourceOptionsAreEqual(DataSourceOptions left, DataSourceOptions right) + { + return left.DisableDataSources == right.DisableDataSources + && left.AutomaticDataSourceSelection == right.AutomaticDataSourceSelection + && left.AutomaticValidation == right.AutomaticValidation + && left.PreselectedDataSourceIds.ToHashSet(StringComparer.Ordinal).SetEquals(right.PreselectedDataSourceIds); + } private string ExtractThreadName(string firstUserInput) { @@ -547,6 +580,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (!this.ComposerState.HasUserDraft && previousChatTemplate != this.currentChatTemplate) this.ComposerState.ApplyTemplate(this.currentChatTemplate); + + await this.ApplyUpdatedStandardDataSourceOptionsAfterConfigurationChange(); } private IReadOnlyList GetAgentSelectedDataSources() diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor b/app/MindWork AI Studio/Components/DataSourceSelection.razor index 29c0d709..c5f1be6c 100644 --- a/app/MindWork AI Studio/Components/DataSourceSelection.razor +++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor @@ -160,13 +160,13 @@ else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE) } - + @if (this.areDataSourcesEnabled) { - - - - + + + + @foreach (var source in this.availableDataSources) { diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs index 0727092f..7f11972a 100644 --- a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs +++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs @@ -52,6 +52,7 @@ public partial class DataSourceSelection : MSGComponentBase private bool aiBasedSourceSelection; private bool aiBasedValidation; private bool areDataSourcesEnabled; + private uint loadAndApplyFiltersGeneration; #region Overrides of ComponentBase @@ -75,15 +76,7 @@ public partial class DataSourceSelection : MSGComponentBase // Right before the preselection would be used to kick off the // RAG process, we will filter the data sources as well. // - var preselectedSources = new List(this.DataSourceOptions.PreselectedDataSourceIds.Count); - foreach (var preselectedDataSourceId in this.DataSourceOptions.PreselectedDataSourceIds) - { - var dataSource = this.SettingsManager.ConfigurationData.DataSources.FirstOrDefault(ds => ds.Id == preselectedDataSourceId); - if (dataSource is not null) - preselectedSources.Add(dataSource); - } - - this.selectedDataSources = preselectedSources; + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); await base.OnInitializedAsync(); } @@ -94,6 +87,7 @@ public partial class DataSourceSelection : MSGComponentBase this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection; this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation; this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources; + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); } switch (this.SelectionMode) @@ -119,7 +113,7 @@ public partial class DataSourceSelection : MSGComponentBase // In configuration mode, we have to load all data sources: // case DataSourceSelectionMode.CONFIGURATION_MODE: - this.availableDataSources = this.SettingsManager.ConfigurationData.DataSources; + this.availableDataSources = this.GetConfiguredDataSourcesSnapshot(); break; } @@ -156,7 +150,7 @@ public partial class DataSourceSelection : MSGComponentBase this.aiBasedSourceSelection = this.DataSourceOptions.AutomaticDataSourceSelection; this.aiBasedValidation = this.DataSourceOptions.AutomaticValidation; this.areDataSourcesEnabled = !this.DataSourceOptions.DisableDataSources; - this.selectedDataSources = this.SettingsManager.ConfigurationData.DataSources.Where(ds => this.DataSourceOptions.PreselectedDataSourceIds.Contains(ds.Id)).ToList(); + this.selectedDataSources = this.GetDataSourcesFromConfiguredIds(); this.waitingForDataSources = false; // @@ -176,20 +170,38 @@ public partial class DataSourceSelection : MSGComponentBase this.showDataSourceSelection = false; this.StateHasChanged(); } + + private IReadOnlyList GetConfiguredDataSourcesSnapshot() => this.SettingsManager.ConfigurationData.DataSources.ToList(); + + private IReadOnlyCollection GetDataSourcesFromConfiguredIds() + { + var preselectedDataSourceIds = this.DataSourceOptions.PreselectedDataSourceIds.ToHashSet(StringComparer.Ordinal); + return this.GetConfiguredDataSourcesSnapshot().Where(ds => preselectedDataSourceIds.Contains(ds.Id)).ToList(); + } private async Task LoadAndApplyFilters() { if(this.DataSourceOptions.DisableDataSources) + { + this.loadAndApplyFiltersGeneration++; return; + } if(this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE) + { + this.loadAndApplyFiltersGeneration++; return; + } + var generation = ++this.loadAndApplyFiltersGeneration; this.waitingForDataSources = true; this.StateHasChanged(); // Load the data sources: var sources = await this.DataSourceService.GetDataSources(this.LLMProvider, this.selectedDataSources); + if (generation != this.loadAndApplyFiltersGeneration) + return; + this.availableDataSources = sources.AllowedDataSources; this.selectedDataSources = sources.SelectedDataSources; this.waitingForDataSources = false; @@ -230,9 +242,38 @@ public partial class DataSourceSelection : MSGComponentBase await this.OptionsChanged(); } + private bool IsPreselectedDataSourcesDisabledLocked() + { + return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE + && ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesDisabled, out var meta) + && meta.IsLocked; + } + + private bool IsPreselectedDataSourcesAutomaticSelectionLocked() + { + return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE + && ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesAutomaticSelection, out var meta) + && meta.IsLocked; + } + + private bool IsPreselectedDataSourcesAutomaticValidationLocked() + { + return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE + && ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourcesAutomaticValidation, out var meta) + && meta.IsLocked; + } + + private bool IsPreselectedDataSourceIdsLocked() + { + return this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE + && ManagedConfiguration.TryGet(x => x.Chat, x => x.PreselectedDataSourceIds, out var meta) + && meta.IsLocked; + } + private async Task OptionsChanged() { this.internalChange = true; + this.loadAndApplyFiltersGeneration++; await this.DataSourceOptionsChanged.InvokeAsync(this.DataSourceOptions); diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentDataSourceSelection.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentDataSourceSelection.razor index e4b258cb..6e951c24 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentDataSourceSelection.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentDataSourceSelection.razor @@ -1,3 +1,4 @@ +@using AIStudio.Settings @inherits SettingsPanelBase @@ -5,7 +6,7 @@ @T("Use Case: this agent is used to select the appropriate data sources for the current prompt.") - - + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentRetrievalContextValidation.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentRetrievalContextValidation.razor index 061c3585..e6bcb880 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentRetrievalContextValidation.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelAgentRetrievalContextValidation.razor @@ -1,16 +1,17 @@ +@using AIStudio.Settings @inherits SettingsPanelBase @T("Use Case: this agent is used to validate any retrieval context of any retrieval process. Perhaps there are many of these retrieval contexts and you want to validate them all. Therefore, you might want to use a cheap and fast LLM for this job. When using a local or self-hosted LLM, look for a small (e.g. 3B) and fast model.") - + @if (this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation) { - - - + + + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor index 348f7a53..f80fa857 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChat.razor @@ -25,7 +25,7 @@ @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 b8690ff4..90cc0e1d 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -270,12 +270,38 @@ CONFIG["SETTINGS"] = {} -- 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" -- +-- +-- Configure default data source options for new chats. +-- +-- Controls whether data sources are off by default: +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesDisabled"] = false + +-- Controls whether AI Studio asks an agent to choose data sources: +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesAutomaticSelection"] = true + +-- Controls whether retrieved data is validated by an agent: +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesAutomaticValidation"] = true + +-- Must contain IDs from CONFIG["DATA_SOURCES"] or user-configured data sources. +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourceIds"] = { +-- "00000000-0000-0000-0000-000000000000", +-- } +-- +-- Configure whether default chat data source options are applied when assistant results are sent to chat. +-- Allowed values are: NO_DATA_SOURCES, APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS +-- CONFIG["SETTINGS"]["DataChat.SendToChatDataSourceBehavior"] = "APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS" +-- -- 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 +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesDisabled.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesAutomaticSelection.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourcesAutomaticValidation.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.PreselectedDataSourceIds.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataChat.SendToChatDataSourceBehavior.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"]. @@ -386,6 +412,26 @@ CONFIG["SETTINGS"] = {} -- "00000000-0000-0000-0000-000000000001", -- } +-- Configure the data source selection agent. +-- This agent is used when chat data source options enable AI-based data source selection. +-- The provider must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"]. +-- CONFIG["SETTINGS"]["DataAgentDataSourceSelection.PreselectAgentOptions"] = true +-- CONFIG["SETTINGS"]["DataAgentDataSourceSelection.PreselectedAgentProvider"] = "00000000-0000-0000-0000-000000000000" +-- CONFIG["SETTINGS"]["DataAgentDataSourceSelection.PreselectAgentOptions.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataAgentDataSourceSelection.PreselectedAgentProvider.AllowUserOverride"] = true + +-- Configure the retrieval context validation agent. +-- This agent is used when retrieval context validation is enabled globally and chat data source options enable AI-based validation. +-- The provider must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"]. +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.EnableRetrievalContextValidation"] = true +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.PreselectAgentOptions"] = true +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.PreselectedAgentProvider"] = "00000000-0000-0000-0000-000000000000" +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.NumParallelValidations"] = 3 +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.EnableRetrievalContextValidation.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.PreselectAgentOptions.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.PreselectedAgentProvider.AllowUserOverride"] = true +-- CONFIG["SETTINGS"]["DataAgentRetrievalContextValidation.NumParallelValidations.AllowUserOverride"] = true + -- Configure assistant plugin security audits. -- -- Configure whether assistant plugins must be audited before users can activate them. diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index 07f9f9e2..327301b7 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -125,9 +125,9 @@ public sealed class Data public DataTextContentCleaner TextContentCleaner { get; init; } = new(); - public DataAgentDataSourceSelection AgentDataSourceSelection { get; init; } = new(); + public DataAgentDataSourceSelection AgentDataSourceSelection { get; init; } = new(x => x.AgentDataSourceSelection); - public DataAgentRetrievalContextValidation AgentRetrievalContextValidation { get; init; } = new(); + public DataAgentRetrievalContextValidation AgentRetrievalContextValidation { get; init; } = new(x => x.AgentRetrievalContextValidation); public DataAssistantPluginAudit AssistantPluginAudit { get; init; } = new(x => x.AssistantPluginAudit); diff --git a/app/MindWork AI Studio/Settings/DataModel/DataAgentDataSourceSelection.cs b/app/MindWork AI Studio/Settings/DataModel/DataAgentDataSourceSelection.cs index 55473dcc..1e2ad3d2 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataAgentDataSourceSelection.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataAgentDataSourceSelection.cs @@ -1,14 +1,23 @@ +using System.Linq.Expressions; + namespace AIStudio.Settings.DataModel; -public sealed class DataAgentDataSourceSelection +public sealed class DataAgentDataSourceSelection(Expression>? configSelection = null) { + /// + /// The default constructor for the JSON deserializer. + /// + public DataAgentDataSourceSelection() : this(null) + { + } + /// /// Preselect any data source selection options? /// - public bool PreselectAgentOptions { get; set; } + public bool PreselectAgentOptions { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectAgentOptions, false); /// /// Preselect a data source selection provider? /// - public string PreselectedAgentProvider { get; set; } = string.Empty; + public string PreselectedAgentProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedAgentProvider, string.Empty); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataAgentRetrievalContextValidation.cs b/app/MindWork AI Studio/Settings/DataModel/DataAgentRetrievalContextValidation.cs index a4ae0a8f..93ae50dd 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataAgentRetrievalContextValidation.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataAgentRetrievalContextValidation.cs @@ -1,24 +1,33 @@ +using System.Linq.Expressions; + namespace AIStudio.Settings.DataModel; -public sealed class DataAgentRetrievalContextValidation +public sealed class DataAgentRetrievalContextValidation(Expression>? configSelection = null) { + /// + /// The default constructor for the JSON deserializer. + /// + public DataAgentRetrievalContextValidation() : this(null) + { + } + /// /// Enable the retrieval context validation agent? /// - public bool EnableRetrievalContextValidation { get; set; } + public bool EnableRetrievalContextValidation { get; set; } = ManagedConfiguration.Register(configSelection, n => n.EnableRetrievalContextValidation, false); /// /// Preselect any retrieval context validation options? /// - public bool PreselectAgentOptions { get; set; } + public bool PreselectAgentOptions { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectAgentOptions, false); /// /// Preselect a retrieval context validation provider? /// - public string PreselectedAgentProvider { get; set; } = string.Empty; + public string PreselectedAgentProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedAgentProvider, string.Empty); /// /// Configure how many parallel validations to run. /// - public int NumParallelValidations { get; set; } = 3; + public int NumParallelValidations { get; set; } = ManagedConfiguration.Register(configSelection, n => n.NumParallelValidations, 3); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs index f2a7ea37..67b3b313 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataChat.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataChat.cs @@ -29,7 +29,7 @@ public sealed class DataChat(Expression>? configSelection = /// /// Defines the data source behavior when sending assistant results to a chat. /// - public SendToChatDataSourceBehavior SendToChatDataSourceBehavior { get; set; } = SendToChatDataSourceBehavior.NO_DATA_SOURCES; + public SendToChatDataSourceBehavior SendToChatDataSourceBehavior { get; set; } = ManagedConfiguration.Register(configSelection, n => n.SendToChatDataSourceBehavior, SendToChatDataSourceBehavior.NO_DATA_SOURCES); /// /// Preselect any chat options? @@ -51,10 +51,47 @@ public sealed class DataChat(Expression>? configSelection = /// public string PreselectedChatTemplate { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedChatTemplate, string.Empty); + /// + /// Whether data sources are disabled by default for new chats. + /// + public bool PreselectedDataSourcesDisabled { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedDataSourcesDisabled, true); + + /// + /// Whether data sources should be selected automatically by default for new chats. + /// + public bool PreselectedDataSourcesAutomaticSelection { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedDataSourcesAutomaticSelection, false); + + /// + /// Whether retrieved data should be validated automatically by default for new chats. + /// + public bool PreselectedDataSourcesAutomaticValidation { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedDataSourcesAutomaticValidation, false); + + /// + /// The data source IDs that should be preselected by default for new chats. + /// + public List PreselectedDataSourceIds { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreselectedDataSourceIds, []); + /// /// Should we preselect data sources options for a created chat? /// - public DataSourceOptions PreselectedDataSourceOptions { get; set; } = new(); + // Compatibility shim: legacy settings used this nested object. See documentation/compatibility-shims/2026-07-chat-data-source-options.md; remove after 2027-01-05. + public DataSourceOptions PreselectedDataSourceOptions + { + get => new() + { + DisableDataSources = this.PreselectedDataSourcesDisabled, + AutomaticDataSourceSelection = this.PreselectedDataSourcesAutomaticSelection, + AutomaticValidation = this.PreselectedDataSourcesAutomaticValidation, + PreselectedDataSourceIds = [..this.PreselectedDataSourceIds], + }; + set + { + this.PreselectedDataSourcesDisabled = value.DisableDataSources; + this.PreselectedDataSourcesAutomaticSelection = value.AutomaticDataSourceSelection; + this.PreselectedDataSourcesAutomaticValidation = value.AutomaticValidation; + this.PreselectedDataSourceIds = [..value.PreselectedDataSourceIds]; + } + } /// /// Should we show the latest message after loading? When false, we show the first (aka oldest) message. diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs index e44fc8dc..2aea5f96 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -435,7 +435,9 @@ public static partial class ManagedConfiguration if(dryRun) return successful; - return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); + var settingName = SettingName(propertyExpression); + var managedMode = ReadManagedConfigurationMode(propertyExpression, settings); + return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName); } /// @@ -1026,6 +1028,10 @@ public static partial class ManagedConfiguration .Cast() .OrderBy(key => key.ToString(), StringComparer.Ordinal) .Select(key => $"{key}:{dictionary[key]}")), + System.Collections.IEnumerable enumerable => string.Join(";", enumerable + .Cast() + .Select(item => item.ToString() ?? string.Empty) + .Order(StringComparer.Ordinal)), IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture), _ => value.ToString() ?? string.Empty, diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index 876c8408..620c3b20 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -358,17 +358,18 @@ public static partial class ManagedConfiguration if (!TryGet(configSelection, propertyExpression, out var configMeta)) return false; + if (configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT) + return CleanupEditableDefaultState(configMeta, SettingName(propertyExpression), availablePlugins.ToList()); + if (configMeta.LockedByConfigPluginId == Guid.Empty || !configMeta.IsLocked) return false; var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.LockedByConfigPluginId); - if (plugin is null) - { - configMeta.ResetLockedConfiguration(); - return true; - } + if (plugin is not null) + return false; - return false; + configMeta.ResetLockedConfiguration(); + return true; } public static bool IsConfigurationLeftOver( diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 2b492d6f..c5478cf1 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -205,6 +205,16 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: data source security settings ManagedConfiguration.TryProcessConfiguration(x => x.DataSourceSecurity, x => x.TrustedProviderIds, this.Id, settingsTable, dryRun); + // Config: data source selection agent settings + ManagedConfiguration.TryProcessConfiguration(x => x.AgentDataSourceSelection, x => x.PreselectAgentOptions, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.AgentDataSourceSelection, x => x.PreselectedAgentProvider, Guid.Empty, this.Id, settingsTable, dryRun); + + // Config: retrieval context validation agent settings + ManagedConfiguration.TryProcessConfiguration(x => x.AgentRetrievalContextValidation, x => x.EnableRetrievalContextValidation, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.AgentRetrievalContextValidation, x => x.PreselectAgentOptions, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.AgentRetrievalContextValidation, x => x.PreselectedAgentProvider, Guid.Empty, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.AgentRetrievalContextValidation, x => x.NumParallelValidations, this.Id, settingsTable, dryRun); + // Config: assistant plugin audit settings ManagedConfiguration.TryProcessConfiguration(x => x.AssistantPluginAudit, x => x.RequireAuditBeforeActivation, this.Id, settingsTable, dryRun); ManagedConfiguration.TryProcessConfiguration(x => x.AssistantPluginAudit, x => x.PreselectedAgentProvider, Guid.Empty, this.Id, settingsTable, dryRun); @@ -250,6 +260,11 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT 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); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourcesDisabled, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourcesAutomaticSelection, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourcesAutomaticValidation, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourceIds, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.SendToChatDataSourceBehavior, 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 3f74c556..42396f9f 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -214,6 +214,21 @@ public static partial class PluginFactory if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedChatTemplate, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedDataSourcesDisabled, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedDataSourcesAutomaticSelection, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedDataSourcesAutomaticValidation, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.PreselectedDataSourceIds, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Chat, x => x.SendToChatDataSourceBehavior, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; // Check for the update interval: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateInterval, AVAILABLE_PLUGINS)) @@ -300,6 +315,26 @@ public static partial class PluginFactory if(ManagedConfiguration.IsConfigurationLeftOver(x => x.DataSourceSecurity, x => x.TrustedProviderIds, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + // Check data source selection agent settings: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentDataSourceSelection, x => x.PreselectAgentOptions, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentDataSourceSelection, x => x.PreselectedAgentProvider, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + // Check retrieval context validation agent settings: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentRetrievalContextValidation, x => x.EnableRetrievalContextValidation, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentRetrievalContextValidation, x => x.PreselectAgentOptions, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentRetrievalContextValidation, x => x.PreselectedAgentProvider, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AgentRetrievalContextValidation, x => x.NumParallelValidations, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + // Check if audit is required before it can be activated if(ManagedConfiguration.IsConfigurationLeftOver(x => x.AssistantPluginAudit, x => x.RequireAuditBeforeActivation, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; diff --git a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs index dbd8954a..d4fcd838 100644 --- a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs +++ b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs @@ -70,9 +70,10 @@ public sealed class DataSourceService private async Task GetDataSources(bool usingTrustedProvider, IReadOnlyCollection? previousSelectedDataSources = null) { - var allDataSources = this.settingsManager.ConfigurationData.DataSources; + var allDataSources = this.settingsManager.ConfigurationData.DataSources.ToList(); + var previousSelectedDataSourceIds = previousSelectedDataSources?.Select(source => source.Id).ToHashSet(StringComparer.Ordinal) ?? []; var filteredDataSources = new List(allDataSources.Count); - var filteredSelectedDataSources = new List(previousSelectedDataSources?.Count ?? 0); + var filteredSelectedDataSources = new List(previousSelectedDataSourceIds.Count); var tasks = new List>(allDataSources.Count); // Start all checks in parallel: @@ -86,7 +87,7 @@ public sealed class DataSourceService if (source is not null) { filteredDataSources.Add(source); - if (previousSelectedDataSources is not null && previousSelectedDataSources.Contains(source)) + if (previousSelectedDataSourceIds.Contains(source.Id)) filteredSelectedDataSources.Add(source); } } @@ -205,4 +206,4 @@ public sealed class DataSourceService return null; } } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.3.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.3.md index 7a923be1..e49cdc1d 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.3.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.3.md @@ -1,5 +1,6 @@ # v26.6.3, build 243 (2026-06-xx xx:xx UTC) - Added expert capability overrides for providers, so advanced users and configuration plugins can manually adjust selected model capabilities, including reasoning (thinking) behavior, when automatic detection needs adjustment. +- Added configuration plugin options for default chat data source behavior and the related data source selection and validation agents. - Improved the provider selection by showing small capability icons for supported audio, image, speech, and reasoning features of the selected model. - Improved source links in chat answers when file or document names contained spaces, umlauts, or other special characters. Source entries now open much more reliably for documents with names such as PDFs from shared portals or internal knowledge bases. - Improved all assistants, so running tasks can continue when you leave the assistant and return later. @@ -8,4 +9,5 @@ - Improved the chat experience by automatically focusing the message composer again when it becomes available. Thanks, Dominic Neuburg (`donework`), for the contribution. - Improved the chat message formatting so line breaks in responses are preserved more closely, making structured text easier to read. Thanks, Dominic Neuburg (`donework`), for the contribution. - Fixed configuration plugins so they can configure assistant plugin audit settings, including required audit levels, audit provider selection, and automatic background audits. +- Fixed default data source selection so sources that require local or trusted providers are selected again after switching from a cloud provider to a suitable provider. - Upgraded Rust to v1.96.1 \ No newline at end of file diff --git a/documentation/compatibility-shims/2026-07-chat-data-source-options.md b/documentation/compatibility-shims/2026-07-chat-data-source-options.md new file mode 100644 index 00000000..22491e9f --- /dev/null +++ b/documentation/compatibility-shims/2026-07-chat-data-source-options.md @@ -0,0 +1,26 @@ +# Chat Data Source Options + +- Status: Active +- Introduced: 2026-07-05 +- Remove after: 2027-01-05 +- Code references: + - `app/MindWork AI Studio/Settings/DataModel/DataChat.cs` + +## User Impact + +Older settings files store default chat data source options in the nested `DataChat.PreselectedDataSourceOptions` object. + +Without this shim, those defaults would be lost after upgrading to a version that exposes the individual data source default fields to configuration plugins. + +## Compatibility Behavior + +`DataChat.PreselectedDataSourceOptions` remains available as a compatibility property. Reading it maps the new individual fields into a `DataSourceOptions` object, and setting it copies values from the legacy nested object into the new fields. + +This lets older settings files load without a settings version migration and keeps existing UI bindings working while configuration plugins manage the individual fields. + +## Removal Checklist + +- Confirm supported settings files are expected to contain `PreselectedDataSourcesDisabled`, `PreselectedDataSourcesAutomaticSelection`, `PreselectedDataSourcesAutomaticValidation`, and `PreselectedDataSourceIds`. +- Remove `DataChat.PreselectedDataSourceOptions`. +- Update any remaining callers to use the individual fields or a dedicated helper. +- Update this document's status to `Removed`.