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))