diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor
index e220d41d..fc466e7a 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor
@@ -13,7 +13,7 @@
-
+
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs
index dded906c..bad3fca3 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs
@@ -15,7 +15,4 @@ public abstract class SettingsPanelBase : MSGComponentBase
[Inject]
protected RustService RustService { get; init; } = null!;
-
- [Inject]
- protected SettingsLocker SettingsLocker { get; init; } = null!;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs
index 2367aff4..cc115a2c 100644
--- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs
+++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs
@@ -215,7 +215,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseEnvironment.ConfigurationId, enterpriseEnvironment.ConfigurationServerUrl);
// Load (but not start) all plugins without waiting for them:
+ #if DEBUG
+ var pluginLoadingTimeout = new CancellationTokenSource();
+ #else
var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ #endif
await PluginFactory.LoadAll(pluginLoadingTimeout.Token);
// Set up hot reloading for plugins:
diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs
index 072dd3ad..a4d9c2b4 100644
--- a/app/MindWork AI Studio/Program.cs
+++ b/app/MindWork AI Studio/Program.cs
@@ -126,7 +126,6 @@ internal sealed class Program
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
- builder.Services.AddSingleton();
builder.Services.AddTransient();
builder.Services.AddTransient();
builder.Services.AddTransient();
diff --git a/app/MindWork AI Studio/Settings/ConfigMeta.cs b/app/MindWork AI Studio/Settings/ConfigMeta.cs
new file mode 100644
index 00000000..f8d50ecc
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/ConfigMeta.cs
@@ -0,0 +1,88 @@
+using System.Linq.Expressions;
+
+using AIStudio.Settings.DataModel;
+
+namespace AIStudio.Settings;
+
+///
+/// Represents configuration metadata for a specific class and property.
+///
+/// The class type that contains the configuration property.
+/// The type of the configuration property value.
+public record ConfigMeta : ConfigMetaBase
+{
+ public ConfigMeta(Expression> configSelection, Expression> propertyExpression)
+ {
+ this.ConfigSelection = configSelection;
+ this.PropertyExpression = propertyExpression;
+ }
+
+ ///
+ /// The expression to select the configuration class from the settings data.
+ ///
+ private Expression> ConfigSelection { get; }
+
+ ///
+ /// The expression to select the property within the configuration class.
+ ///
+ private Expression> PropertyExpression { get; }
+
+ ///
+ /// Indicates whether the configuration is managed by a plugin and is therefore locked.
+ ///
+ public bool IsLocked { get; private set; }
+
+ ///
+ /// The ID of the plugin that manages this configuration. This is set when the configuration is locked.
+ ///
+ public Guid MangedByConfigPluginId { get; private set; }
+
+ ///
+ /// The default value for the configuration property. This is used when resetting the property to its default state.
+ ///
+ public required TValue Default { get; init; }
+
+ ///
+ /// Locks the configuration state, indicating that it is managed by a specific plugin.
+ ///
+ /// The ID of the plugin that is managing this configuration.
+ public void LockManagedState(Guid pluginId)
+ {
+ this.IsLocked = true;
+ this.MangedByConfigPluginId = pluginId;
+ }
+
+ ///
+ /// Resets the managed state of the configuration, allowing it to be modified again.
+ /// This will also reset the property to its default value.
+ ///
+ public void ResetManagedState()
+ {
+ this.IsLocked = false;
+ this.MangedByConfigPluginId = Guid.Empty;
+ this.Reset();
+ }
+
+ ///
+ /// Resets the configuration property to its default value.
+ ///
+ public void Reset()
+ {
+ var configInstance = this.ConfigSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
+ var memberExpression = this.PropertyExpression.GetMemberExpression();
+ if (memberExpression.Member is System.Reflection.PropertyInfo propertyInfo)
+ propertyInfo.SetValue(configInstance, this.Default);
+ }
+
+ ///
+ /// Sets the value of the configuration property to the specified value.
+ ///
+ /// The value to set for the configuration property.
+ public void SetValue(TValue value)
+ {
+ var configInstance = this.ConfigSelection.Compile().Invoke(SETTINGS_MANAGER.ConfigurationData);
+ var memberExpression = this.PropertyExpression.GetMemberExpression();
+ 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/ConfigMetaBase.cs b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs
new file mode 100644
index 00000000..4ef74e88
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/ConfigMetaBase.cs
@@ -0,0 +1,6 @@
+namespace AIStudio.Settings;
+
+public abstract record ConfigMetaBase : IConfig
+{
+ protected static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService();
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs
index 695d2ad8..0e825aa0 100644
--- a/app/MindWork AI Studio/Settings/DataModel/Data.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs
@@ -71,7 +71,7 @@ public sealed class Data
///
public uint NextChatTemplateNum { get; set; } = 1;
- public DataApp App { get; init; } = new();
+ public DataApp App { get; init; } = new(x => x.App);
public DataChat Chat { get; init; } = new();
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
index 022cd8c8..477afa30 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs
@@ -1,7 +1,16 @@
+using System.Linq.Expressions;
+
namespace AIStudio.Settings.DataModel;
-public sealed class DataApp
+public sealed class DataApp(Expression>? configSelection = null)
{
+ ///
+ /// The default constructor for the JSON deserializer.
+ ///
+ public DataApp() : this(null)
+ {
+ }
+
///
/// The language behavior.
///
@@ -21,7 +30,7 @@ public sealed class DataApp
/// Should we save energy? When true, we will update content streamed
/// from the server, i.e., AI, less frequently.
///
- public bool IsSavingEnergy { get; set; }
+ public bool IsSavingEnergy { get; set; } = ManagedConfiguration.Register(configSelection, n => n.IsSavingEnergy, false);
///
/// Should we enable spellchecking for all input fields?
@@ -31,7 +40,7 @@ public sealed class DataApp
///
/// If and when we should look for updates.
///
- public UpdateBehavior UpdateBehavior { get; set; } = UpdateBehavior.HOURLY;
+ public UpdateBehavior UpdateBehavior { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UpdateBehavior, UpdateBehavior.HOURLY);
///
/// The navigation behavior.
@@ -41,7 +50,7 @@ public sealed class DataApp
///
/// The visibility setting for previews features.
///
- public PreviewVisibility PreviewVisibility { get; set; } = PreviewVisibility.NONE;
+ public PreviewVisibility PreviewVisibility { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreviewVisibility, PreviewVisibility.NONE);
///
/// The enabled preview features.
@@ -66,5 +75,5 @@ public sealed class DataApp
///
/// Should the user be allowed to add providers?
///
- public bool AllowUserToAddProvider { get; set; } = true;
+ public bool AllowUserToAddProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddProvider, true);
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV4.cs b/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV4.cs
index ecaebe8a..61555a3c 100644
--- a/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV4.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV4.cs
@@ -33,7 +33,7 @@ public sealed class DataV4
///
public uint NextProfileNum { get; set; } = 1;
- public DataApp App { get; init; } = new();
+ public DataApp App { get; init; } = new(x => x.App);
public DataChat Chat { get; init; } = new();
diff --git a/app/MindWork AI Studio/Settings/ExpressionExtensions.cs b/app/MindWork AI Studio/Settings/ExpressionExtensions.cs
index 582bf65c..5d567c03 100644
--- a/app/MindWork AI Studio/Settings/ExpressionExtensions.cs
+++ b/app/MindWork AI Studio/Settings/ExpressionExtensions.cs
@@ -6,7 +6,7 @@ public static class ExpressionExtensions
{
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(typeof(ExpressionExtensions));
- public static MemberExpression GetMemberExpression(this Expression> expression)
+ public static MemberExpression GetMemberExpression(this Expression> expression)
{
switch (expression.Body)
{
diff --git a/app/MindWork AI Studio/Settings/IConfig.cs b/app/MindWork AI Studio/Settings/IConfig.cs
new file mode 100644
index 00000000..c9db8958
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/IConfig.cs
@@ -0,0 +1,3 @@
+namespace AIStudio.Settings;
+
+public interface IConfig;
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs
new file mode 100644
index 00000000..3767cd66
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs
@@ -0,0 +1,198 @@
+using System.Collections.Concurrent;
+using System.Linq.Expressions;
+
+using AIStudio.Settings.DataModel;
+using AIStudio.Tools.PluginSystem;
+
+using Lua;
+
+namespace AIStudio.Settings;
+
+public static class ManagedConfiguration
+{
+ private static readonly ConcurrentDictionary METADATA = new();
+
+ ///
+ /// Registers a configuration setting with a default value.
+ ///
+ ///
+ /// When called from the JSON deserializer, the configSelection parameter will be null.
+ /// In this case, the method will return the default value without registering the setting.
+ ///
+ /// The expression to select the configuration class.
+ /// The expression to select the property within the configuration class.
+ /// The default value to use when the setting is not configured.
+ /// The type of the configuration class.
+ /// The type of the property within the configuration class.
+ /// The default value.
+ public static TValue Register(Expression>? configSelection, Expression> propertyExpression, TValue defaultValue)
+ {
+ // When called from the JSON deserializer by using the standard constructor,
+ // we ignore the register call and return the default value:
+ if(configSelection is null)
+ return defaultValue;
+
+ var configPath = Path(configSelection, propertyExpression);
+
+ // If the metadata already exists for this configuration path, we return the default value:
+ if (METADATA.ContainsKey(configPath))
+ return defaultValue;
+
+ METADATA[configPath] = new ConfigMeta(configSelection, propertyExpression)
+ {
+ Default = defaultValue,
+ };
+
+ return defaultValue;
+ }
+
+ ///
+ /// Attempts to retrieve the configuration metadata for a given configuration selection and property expression.
+ ///
+ ///
+ /// When no configuration metadata is found, it returns a NoConfig instance with the default value set to default(TValue).
+ /// This allows the caller to handle the absence of configuration gracefully. In such cases, the return value of the method will be false.
+ ///
+ /// The expression to select the configuration class.
+ /// The expression to select the property within the configuration class.
+ /// The output parameter that will hold the configuration metadata if found.
+ /// The type of the configuration class.
+ /// The type of the property within the configuration class.
+ /// True if the configuration metadata was found, otherwise false.
+ public static bool TryGet(Expression> configSelection, Expression> propertyExpression, out ConfigMeta configMeta)
+ {
+ var configPath = Path(configSelection, propertyExpression);
+ if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta meta)
+ {
+ configMeta = meta;
+ return true;
+ }
+
+ configMeta = new NoConfig(configSelection, propertyExpression)
+ {
+ Default = default!,
+ };
+
+ return false;
+ }
+
+ ///
+ /// Attempts to process the configuration settings from a Lua table.
+ ///
+ ///
+ /// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
+ /// Furthermore, it locks the managed state of the configuration metadata to the provided configuration plugin ID.
+ /// The setting's value is set to the configured value.
+ ///
+ /// 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.
+ /// True when the configuration was successfully processed, otherwise false.
+ public static bool TryProcessConfiguration(Expression> configSelection, Expression> propertyExpression, Guid configPluginId, LuaTable settings, bool dryRun)
+ {
+ if(!TryGet(configSelection, propertyExpression, out var configMeta))
+ return false;
+
+ var (configuredValue, successful) = configMeta.Default switch
+ {
+ Enum => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue) && configuredEnumValue.TryRead(out var configuredEnumText) && Enum.TryParse(typeof(TValue), configuredEnumText, true, out var configuredEnum) ? ((TValue)configuredEnum, true) : (configMeta.Default, false),
+ Guid => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredGuidValue) && configuredGuidValue.TryRead(out var configuredGuidText) && Guid.TryParse(configuredGuidText, out var configuredGuid) ? ((TValue)(object)configuredGuid, true) : (configMeta.Default, false),
+
+ string => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue) && configuredTextValue.TryRead(out var configuredText) ? ((TValue)(object)configuredText, true) : (configMeta.Default, false),
+ bool => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredBoolValue) && configuredBoolValue.TryRead(out var configuredState) ? ((TValue)(object)configuredState, true) : (configMeta.Default, false),
+
+ int => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredIntValue) && configuredIntValue.TryRead(out var configuredInt) ? ((TValue)(object)configuredInt, true) : (configMeta.Default, false),
+ double => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredDoubleValue) && configuredDoubleValue.TryRead(out var configuredDouble) ? ((TValue)(object)configuredDouble, true) : (configMeta.Default, false),
+ float => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredFloatValue) && configuredFloatValue.TryRead(out var configuredFloat) ? ((TValue)(object)configuredFloat, true) : (configMeta.Default, false),
+
+ _ => (configMeta.Default, false),
+ };
+
+ if(dryRun)
+ return successful;
+
+ switch (successful)
+ {
+ case true:
+ //
+ // Case: the setting was configured, and we could read the value successfully.
+ //
+ configMeta.SetValue(configuredValue);
+ configMeta.LockManagedState(configPluginId);
+ break;
+
+ case false when configMeta.IsLocked && configMeta.MangedByConfigPluginId == configPluginId:
+ //
+ // Case: the setting was configured previously, but we could not read the value successfully.
+ // This happens when the setting was removed from the configuration plugin. We handle that
+ // case only when the setting was locked and managed by the same configuration plugin.
+ //
+ // The other case, when the setting was locked and managed by a different configuration plugin,
+ // is handled by the IsConfigurationLeftOver method, which checks if the configuration plugin
+ // is still available. If it is not available, it resets the managed state of the
+ // configuration setting, allowing it to be reconfigured by a different plugin or left unchanged.
+ //
+ configMeta.ResetManagedState();
+ break;
+
+ case false:
+ //
+ // Case: the setting was not configured, or we could not read the value successfully.
+ // We do not change the setting, and it remains at whatever value it had before.
+ //
+ break;
+ }
+
+ return successful;
+ }
+
+ ///
+ /// Checks if a configuration setting is left over from a configuration plugin that is no longer available.
+ /// If the configuration setting is locked and managed by a configuration plugin that is not available,
+ /// it resets the managed state of the configuration setting and returns true.
+ /// Otherwise, it returns false.
+ ///
+ /// The expression to select the configuration class.
+ /// The expression to select the property within the configuration class.
+ /// The collection of available plugins to check against.
+ /// The type of the configuration class.
+ /// The type of the property within the configuration class.
+ /// True if the configuration setting is left over and was reset, otherwise false.
+ public static bool IsConfigurationLeftOver(Expression> configSelection, Expression> propertyExpression, IEnumerable availablePlugins)
+ {
+ if (!TryGet(configSelection, propertyExpression, out var configMeta))
+ return false;
+
+ if(configMeta.MangedByConfigPluginId == Guid.Empty || !configMeta.IsLocked)
+ return false;
+
+ // Check if the configuration plugin ID is valid against the available plugin IDs:
+ var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.MangedByConfigPluginId);
+ if (plugin is null)
+ {
+ // Remove the locked state:
+ configMeta.ResetManagedState();
+ return true;
+ }
+
+ return false;
+ }
+
+ private static string Path(Expression> configSelection, Expression> propertyExpression)
+ {
+ var className = typeof(TClass).Name;
+
+ var memberExpressionConfig = configSelection.GetMemberExpression();
+ var configName = memberExpressionConfig.Member.Name;
+
+ var memberExpressionProperty = propertyExpression.GetMemberExpression();
+ var propertyName = memberExpressionProperty.Member.Name;
+
+ var configPath = $"{configName}.{className}.{propertyName}";
+ return configPath;
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/NoConfig.cs b/app/MindWork AI Studio/Settings/NoConfig.cs
new file mode 100644
index 00000000..79a1f9dd
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/NoConfig.cs
@@ -0,0 +1,12 @@
+using System.Linq.Expressions;
+
+using AIStudio.Settings.DataModel;
+
+namespace AIStudio.Settings;
+
+public sealed record NoConfig : ConfigMeta
+{
+ public NoConfig(Expression> configSelection, Expression> propertyExpression) : base(configSelection, propertyExpression)
+ {
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/SettingsLocker.cs b/app/MindWork AI Studio/Settings/SettingsLocker.cs
deleted file mode 100644
index cc4a918f..00000000
--- a/app/MindWork AI Studio/Settings/SettingsLocker.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.Linq.Expressions;
-
-namespace AIStudio.Settings;
-
-public sealed class SettingsLocker
-{
- private readonly Dictionary> lockedProperties = new();
-
- ///
- /// Registers a property of a class to be locked by a specific configuration plugin ID.
- ///
- /// The property expression to lock.
- /// The ID of the configuration plugin that locks the property.
- /// The type of the class that contains the property.
- public void Register(Expression> propertyExpression, Guid configurationPluginId)
- {
- var memberExpression = propertyExpression.GetMemberExpression();
- var className = typeof(T).Name;
- var propertyName = memberExpression.Member.Name;
-
- if (!this.lockedProperties.ContainsKey(className))
- this.lockedProperties[className] = [];
-
- this.lockedProperties[className].TryAdd(propertyName, configurationPluginId);
- }
-
- ///
- /// Removes the lock for a property of a class.
- ///
- /// The property expression to remove the lock for.
- /// The type of the class that contains the property.
- public void Remove(Expression> propertyExpression)
- {
- var memberExpression = propertyExpression.GetMemberExpression();
- var className = typeof(T).Name;
- var propertyName = memberExpression.Member.Name;
-
- if (this.lockedProperties.TryGetValue(className, out var props))
- {
- if (props.Remove(propertyName))
- {
- // If the property was removed, check if the class has no more locked properties:
- if (props.Count == 0)
- this.lockedProperties.Remove(className);
- }
- }
- }
-
- ///
- /// Gets the configuration plugin ID that locks a specific property of a class.
- ///
- ///
- ///
- ///
- public Guid GetConfigurationPluginId(Expression> propertyExpression)
- {
- var memberExpression = propertyExpression.GetMemberExpression();
- var className = typeof(T).Name;
- var propertyName = memberExpression.Member.Name;
-
- if (this.lockedProperties.TryGetValue(className, out var props) && props.TryGetValue(propertyName, out var configurationPluginId))
- return configurationPluginId;
-
- // No configuration plugin ID found for this property:
- return Guid.Empty;
- }
-
- public bool IsLocked(Expression> propertyExpression)
- {
- var memberExpression = propertyExpression.GetMemberExpression();
- var className = typeof(T).Name;
- var propertyName = memberExpression.Member.Name;
-
- return this.lockedProperties.TryGetValue(className, out var props) && props.ContainsKey(propertyName);
- }
-}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs
index cb376479..059d0f12 100644
--- a/app/MindWork AI Studio/Settings/SettingsManager.cs
+++ b/app/MindWork AI Studio/Settings/SettingsManager.cs
@@ -349,7 +349,7 @@ public sealed class SettingsManager
}
}
- public static string ToSettingName(Expression> propertyExpression)
+ public static string ToSettingName(Expression> propertyExpression)
{
MemberExpression? memberExpr;
@@ -363,6 +363,6 @@ public sealed class SettingsManager
throw new ArgumentException("Expression must be a property access", nameof(propertyExpression));
// Return the full name of the property, including the class name:
- return $"{typeof(T).Name}.{memberExpr.Member.Name}";
+ return $"{typeof(TIn).Name}.{memberExpr.Member.Name}";
}
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/SettingsMigrations.cs b/app/MindWork AI Studio/Settings/SettingsMigrations.cs
index 98482ceb..7c5a4293 100644
--- a/app/MindWork AI Studio/Settings/SettingsMigrations.cs
+++ b/app/MindWork AI Studio/Settings/SettingsMigrations.cs
@@ -137,7 +137,7 @@ public static class SettingsMigrations
Providers = previousConfig.Providers,
NextProviderNum = previousConfig.NextProviderNum,
- App = new()
+ App = new(x => x.App)
{
EnableSpellchecking = previousConfig.EnableSpellchecking,
IsSavingEnergy = previousConfig.IsSavingEnergy,
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
index 1988e8de..7de7137f 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs
@@ -1,6 +1,5 @@
using AIStudio.Provider;
using AIStudio.Settings;
-using AIStudio.Settings.DataModel;
using Lua;
@@ -13,24 +12,27 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration));
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger();
- private static readonly SettingsLocker SETTINGS_LOCKER = Program.SERVICE_PROVIDER.GetRequiredService();
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService();
- public async Task InitializeAsync()
+ public async Task InitializeAsync(bool dryRun)
{
- if(!this.TryProcessConfiguration(out var issue))
+ if(!this.TryProcessConfiguration(dryRun, out var issue))
this.pluginIssues.Add(issue);
-
- await SETTINGS_MANAGER.StoreSettings();
- await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED);
+
+ if (!dryRun)
+ {
+ await SETTINGS_MANAGER.StoreSettings();
+ await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED);
+ }
}
-
+
///
/// Tries to initialize the UI text content of the plugin.
///
+ /// When true, the method will not apply any changes, but only check if the configuration can be read.
/// The error message, when the UI text content could not be read.
/// True, when the UI text content could be read successfully.
- private bool TryProcessConfiguration(out string message)
+ private bool TryProcessConfiguration(bool dryRun, out string message)
{
// Ensure that the main CONFIG table exists and is a valid Lua table:
if (!this.state.Environment["CONFIG"].TryRead(out var mainTable))
@@ -40,25 +42,21 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
}
//
+ // ===========================================
// Configured settings
+ // ===========================================
//
if (!mainTable.TryGetValue("SETTINGS", out var settingsValue) || !settingsValue.TryRead(out var settingsTable))
{
message = TB("The SETTINGS table does not exist or is not a valid table.");
return false;
}
-
- if (settingsTable.TryGetValue(SettingsManager.ToSettingName(x => x.UpdateBehavior), out var updateBehaviorValue) && updateBehaviorValue.TryRead(out var updateBehaviorText) && Enum.TryParse(updateBehaviorText, true, out var updateBehavior))
- {
- SETTINGS_LOCKER.Register(x => x.UpdateBehavior, this.Id);
- SETTINGS_MANAGER.ConfigurationData.App.UpdateBehavior = updateBehavior;
- }
- if (settingsTable.TryGetValue(SettingsManager.ToSettingName(x => x.AllowUserToAddProvider), out var dontAllowUserToAddProviderValue) && dontAllowUserToAddProviderValue.TryRead(out var dontAllowUserToAddProviderEntry))
- {
- SETTINGS_LOCKER.Register(x => x.AllowUserToAddProvider, this.Id);
- SETTINGS_MANAGER.ConfigurationData.App.AllowUserToAddProvider = dontAllowUserToAddProviderEntry;
- }
+ // Check for updates, and if so, how often?
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UpdateBehavior, this.Id, settingsTable, dryRun);
+
+ // Allow the user to add providers?
+ ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
//
// Configured providers
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
index 244e3984..667ed867 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs
@@ -1,5 +1,6 @@
using System.Text;
+using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using Lua;
@@ -120,8 +121,10 @@ public static partial class PluginFactory
}
//
+ // =========================================================
// Next, we have to clean up our settings. It is possible that a configuration plugin was removed.
// We have to remove the related settings as well:
+ // =========================================================
//
var wasConfigurationChanged = false;
@@ -150,23 +153,18 @@ public static partial class PluginFactory
#pragma warning restore MWAIS0001
//
+ // ==========================================================
// Check all possible settings:
+ // ==========================================================
//
- if (SETTINGS_LOCKER.GetConfigurationPluginId(x => x.UpdateBehavior) is var updateBehaviorPluginId && updateBehaviorPluginId != Guid.Empty)
- {
- var sourcePlugin = AVAILABLE_PLUGINS.FirstOrDefault(plugin => plugin.Id == updateBehaviorPluginId);
- if (sourcePlugin is null)
- {
- // Remove the locked state:
- SETTINGS_LOCKER.Remove(x => x.UpdateBehavior);
-
- // Reset the setting to the default value:
- SETTINGS_MANAGER.ConfigurationData.App.UpdateBehavior = UpdateBehavior.HOURLY;
-
- LOG.LogWarning($"The configured update behavior is based on a plugin that is not available anymore. Resetting the setting to the default value: {SETTINGS_MANAGER.ConfigurationData.App.UpdateBehavior}.");
- wasConfigurationChanged = true;
- }
- }
+
+ // Check for updates, and if so, how often?
+ if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateBehavior, AVAILABLE_PLUGINS))
+ wasConfigurationChanged = true;
+
+ // Allow the user to add providers?
+ if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.AllowUserToAddProvider, AVAILABLE_PLUGINS))
+ wasConfigurationChanged = true;
if (wasConfigurationChanged)
{
@@ -225,7 +223,7 @@ public static partial class PluginFactory
case PluginType.CONFIGURATION:
var configPlug = new PluginConfiguration(isInternal, state, type);
- await configPlug.InitializeAsync();
+ await configPlug.InitializeAsync(true);
return configPlug;
default:
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs
index 983b84da..0943a48e 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs
@@ -103,7 +103,7 @@ public static partial class PluginFactory
languagePlugin.SetBaseLanguage(BASE_LANGUAGE_PLUGIN);
if(plugin is PluginConfiguration configPlugin)
- await configPlugin.InitializeAsync();
+ await configPlugin.InitializeAsync(false);
LOG.LogInformation($"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'");
return plugin;
diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
index 2d7b38b0..1fd30fb2 100644
--- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
+++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs
@@ -6,7 +6,6 @@ public static partial class PluginFactory
{
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginFactory));
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService();
- private static readonly SettingsLocker SETTINGS_LOCKER = Program.SERVICE_PROVIDER.GetRequiredService();
private static bool IS_INITIALIZED;
private static string DATA_DIR = string.Empty;