diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor index a07fc65f..404ac753 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor @@ -25,7 +25,7 @@ var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList(); if (availablePreviewFeatures.Count > 0) { - + } } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs index 81c2b7e5..f5290c88 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor.cs @@ -27,7 +27,30 @@ public partial class SettingsPanelApp : SettingsPanelBase private void UpdatePreviewFeatures(PreviewVisibility previewVisibility) { this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility; - this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures); + var filtered = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures); + filtered.UnionWith(this.GetManagedPreviewFeatures()); + this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = filtered; + } + + private HashSet GetManagedPreviewFeatures() + { + if (ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.HasManagedValue) + return meta.ManagedValue.Where(x => !x.IsReleased()).ToHashSet(); + + return []; + } + + private HashSet GetSelectedPreviewFeatures() + { + var enabled = this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures.Where(x => !x.IsReleased()).ToHashSet(); + enabled.UnionWith(this.GetManagedPreviewFeatures()); + return enabled; + } + + private void UpdateEnabledPreviewFeatures(HashSet selectedFeatures) + { + selectedFeatures.UnionWith(this.GetManagedPreviewFeatures()); + this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedFeatures; } private async Task UpdateLangBehaviour(LangBehavior behavior) @@ -41,4 +64,4 @@ public partial class SettingsPanelApp : SettingsPanelBase this.SettingsManager.ConfigurationData.App.LanguagePluginId = pluginId; await this.MessageBus.SendMessage(this, Event.PLUGINS_RELOADED); } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Settings/ConfigMeta.cs b/app/MindWork AI Studio/Settings/ConfigMeta.cs index f8d50ecc..7789ad86 100644 --- a/app/MindWork AI Studio/Settings/ConfigMeta.cs +++ b/app/MindWork AI Studio/Settings/ConfigMeta.cs @@ -42,6 +42,21 @@ public record ConfigMeta : ConfigMetaBase /// public required TValue Default { get; init; } + /// + /// Indicates whether a managed value is available. + /// + public bool HasManagedValue { get; private set; } + + /// + /// The managed value provided by a configuration plugin. + /// + public TValue ManagedValue { get; private set; } = default!; + + /// + /// The ID of the plugin that provided the managed value. + /// + public Guid ManagedValueByConfigPluginId { get; private set; } + /// /// Locks the configuration state, indicating that it is managed by a specific plugin. /// @@ -62,6 +77,35 @@ public record ConfigMeta : ConfigMetaBase this.MangedByConfigPluginId = Guid.Empty; this.Reset(); } + + /// + /// Unlocks the configuration state without changing the current value. + /// + public void UnlockManagedState() + { + this.IsLocked = false; + this.MangedByConfigPluginId = Guid.Empty; + } + + /// + /// Stores a managed value provided by a configuration plugin. + /// + public void SetManagedValue(TValue value, Guid pluginId) + { + this.ManagedValue = value; + this.ManagedValueByConfigPluginId = pluginId; + this.HasManagedValue = true; + } + + /// + /// Clears the managed value without changing the current value. + /// + public void ClearManagedValue() + { + this.ManagedValue = default!; + this.ManagedValueByConfigPluginId = Guid.Empty; + this.HasManagedValue = false; + } /// /// Resets the configuration property to its default value. @@ -85,4 +129,4 @@ public record ConfigMeta : ConfigMetaBase if (memberExpression.Member is System.Reflection.PropertyInfo propertyInfo) propertyInfo.SetValue(configInstance, value); } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs index 99b95203..c4b58161 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.Parsing.cs @@ -581,6 +581,90 @@ public static partial class ManagedConfiguration return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue); } + + /// + /// Attempts to process the configuration settings from a Lua table for enum set types. + /// The configured values are merged into the existing set and the setting is left unlocked + /// so users can add additional values. + /// + /// The ID of the related configuration plugin. + /// The Lua table containing the settings to process. + /// The expression to select the configuration class. + /// The expression to select the property within the configuration class. + /// When true, the method will not apply any changes but only check if the configuration can be read. + /// The type of the configuration class. + /// The type of the property within the configuration class. It is also the type of the set + /// elements, which must be an enum. + /// True when the configuration was successfully processed, otherwise false. + public static bool TryProcessConfigurationAdditive( + Expression> configSelection, + Expression>> propertyExpression, + Guid configPluginId, + LuaTable settings, + bool dryRun) + where TValue : Enum + { + // + // Handle configured enum sets (additive merge) + // + + // Check if that configuration was registered: + if (!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + var successful = false; + var configuredValue = new HashSet(); + + // Step 1 -- try to read the Lua value (we expect a table) out of the Lua table: + if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) && + configuredLuaList.Type is LuaValueType.Table && + configuredLuaList.TryRead(out var valueTable)) + { + // Determine the length of the Lua table and prepare a set to hold the parsed values: + var len = valueTable.ArrayLength; + var set = new HashSet(len); + + // Iterate over each entry in the Lua table: + for (var index = 1; index <= len; index++) + { + // Retrieve the Lua value at the current index: + var value = valueTable[index]; + + // Step 2 -- try to read the Lua value as a string: + if (value.Type is LuaValueType.String && value.TryRead(out var configuredLuaValueText)) + { + // Step 3 -- try to parse the string as the target type: + if (Enum.TryParse(typeof(TValue), configuredLuaValueText, true, out var configuredEnum)) + set.Add((TValue)configuredEnum); + } + } + + configuredValue = set; + successful = true; + } + + if (dryRun) + return successful; + + if (successful) + { + var configInstance = configSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData); + var currentValue = propertyExpression.Compile().Invoke(configInstance); + var merged = new HashSet(currentValue); + merged.UnionWith(configuredValue); + configMeta.SetValue(merged); + configMeta.SetManagedValue(new HashSet(configuredValue), configPluginId); + } + else if (configMeta.HasManagedValue && configMeta.ManagedValueByConfigPluginId == configPluginId) + { + configMeta.ClearManagedValue(); + } + + if (configMeta.IsLocked && configMeta.MangedByConfigPluginId == configPluginId) + configMeta.UnlockManagedState(); + + return successful; + } /// /// Attempts to process the configuration settings from a Lua table for string set types. @@ -773,4 +857,4 @@ public static partial class ManagedConfiguration return successful; } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index 5cc7a700..427ac5a3 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -9,6 +9,7 @@ namespace AIStudio.Settings; public static partial class ManagedConfiguration { private static readonly ConcurrentDictionary METADATA = new(); + private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); /// /// Attempts to retrieve the configuration metadata for a given configuration selection and @@ -353,6 +354,31 @@ public static partial class ManagedConfiguration return false; } + /// + /// Checks if a managed value is left over from a configuration plugin that is no longer available. + /// If so, it clears the managed value and returns true. + /// + public static bool IsManagedValueLeftOver( + Expression> configSelection, + Expression>> propertyExpression, + IEnumerable availablePlugins) + { + if (!TryGet(configSelection, propertyExpression, out var configMeta)) + return false; + + if (!configMeta.HasManagedValue || configMeta.ManagedValueByConfigPluginId == Guid.Empty) + return false; + + var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.ManagedValueByConfigPluginId); + if (plugin is null) + { + configMeta.ClearManagedValue(); + return true; + } + + return false; + } + public static bool IsConfigurationLeftOver( Expression> configSelection, Expression>> propertyExpression, @@ -387,4 +413,4 @@ public static partial class ManagedConfiguration var configPath = $"{configName}.{className}.{propertyName}"; return configPath; } -} \ No newline at end of file +} diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index a8e10d5d..bfaf31f2 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -108,8 +108,8 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: preview features visibility ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreviewVisibility, this.Id, settingsTable, dryRun); - // Config: enabled preview features - ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.EnabledPreviewFeatures, this.Id, settingsTable, dryRun); + // Config: enabled preview features (additive; users can enable additional features) + ManagedConfiguration.TryProcessConfigurationAdditive(x => x.App, x => x.EnabledPreviewFeatures, this.Id, settingsTable, dryRun); // Config: hide some assistants? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.HiddenAssistants, 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 4bfbe3a3..99bae7fc 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -180,6 +180,8 @@ public static partial class PluginFactory // Check for enabled preview features: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.EnabledPreviewFeatures, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + if(ManagedConfiguration.IsManagedValueLeftOver(x => x.App, x => x.EnabledPreviewFeatures, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; // Check for the transcription provider: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UseTranscriptionProvider, AVAILABLE_PLUGINS))