mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-08-20 14:12:56 +00:00
Replaced SettingsLocker
with ManagedConfiguration
for improved extensibility and streamlined property management. Refactored related components and logic.
This commit is contained in:
parent
7a35626c91
commit
0bd82ad6d5
@ -13,7 +13,7 @@
|
||||
<ConfigurationSelect OptionDescription="@T("Color theme")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="@T("Choose the color theme that best suits for you.")"/>
|
||||
<ConfigurationOption OptionDescription="@T("Save energy?")" LabelOn="@T("Energy saving is enabled")" LabelOff="@T("Energy saving is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="@T("When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available.")"/>
|
||||
<ConfigurationOption OptionDescription="@T("Enable spellchecking?")" LabelOn="@T("Spellchecking is enabled")" LabelOff="@T("Spellchecking is disabled")" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="@T("When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections.")"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="@T("How often should we check for app updates?")" IsLocked="() => this.SettingsLocker.IsLocked<DataApp>(x => x.UpdateBehavior)"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="@T("How often should we check for app updates?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UpdateBehavior, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Navigation bar behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="@T("Select the desired behavior for the navigation bar.")"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Preview feature visibility")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@this.UpdatePreviewFeatures" OptionHelp="@T("Do you want to show preview features in the app?")"/>
|
||||
|
||||
|
@ -15,7 +15,4 @@ public abstract class SettingsPanelBase : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected SettingsLocker SettingsLocker { get; init; } = null!;
|
||||
}
|
@ -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:
|
||||
|
@ -126,7 +126,6 @@ internal sealed class Program
|
||||
builder.Services.AddSingleton<SettingsManager>();
|
||||
builder.Services.AddSingleton<ThreadSafeRandom>();
|
||||
builder.Services.AddSingleton<DataSourceService>();
|
||||
builder.Services.AddSingleton<SettingsLocker>();
|
||||
builder.Services.AddTransient<HTMLParser>();
|
||||
builder.Services.AddTransient<AgentDataSourceSelection>();
|
||||
builder.Services.AddTransient<AgentRetrievalContextValidation>();
|
||||
|
88
app/MindWork AI Studio/Settings/ConfigMeta.cs
Normal file
88
app/MindWork AI Studio/Settings/ConfigMeta.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Represents configuration metadata for a specific class and property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TClass">The class type that contains the configuration property.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the configuration property value.</typeparam>
|
||||
public record ConfigMeta<TClass, TValue> : ConfigMetaBase
|
||||
{
|
||||
public ConfigMeta(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression)
|
||||
{
|
||||
this.ConfigSelection = configSelection;
|
||||
this.PropertyExpression = propertyExpression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The expression to select the configuration class from the settings data.
|
||||
/// </summary>
|
||||
private Expression<Func<Data, TClass>> ConfigSelection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The expression to select the property within the configuration class.
|
||||
/// </summary>
|
||||
private Expression<Func<TClass, TValue>> PropertyExpression { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the configuration is managed by a plugin and is therefore locked.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the plugin that manages this configuration. This is set when the configuration is locked.
|
||||
/// </summary>
|
||||
public Guid MangedByConfigPluginId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value for the configuration property. This is used when resetting the property to its default state.
|
||||
/// </summary>
|
||||
public required TValue Default { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Locks the configuration state, indicating that it is managed by a specific plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginId">The ID of the plugin that is managing this configuration.</param>
|
||||
public void LockManagedState(Guid pluginId)
|
||||
{
|
||||
this.IsLocked = true;
|
||||
this.MangedByConfigPluginId = pluginId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the managed state of the configuration, allowing it to be modified again.
|
||||
/// This will also reset the property to its default value.
|
||||
/// </summary>
|
||||
public void ResetManagedState()
|
||||
{
|
||||
this.IsLocked = false;
|
||||
this.MangedByConfigPluginId = Guid.Empty;
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the configuration property to its default value.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the configuration property to the specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to set for the configuration property.</param>
|
||||
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);
|
||||
}
|
||||
}
|
6
app/MindWork AI Studio/Settings/ConfigMetaBase.cs
Normal file
6
app/MindWork AI Studio/Settings/ConfigMetaBase.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public abstract record ConfigMetaBase : IConfig
|
||||
{
|
||||
protected static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
}
|
@ -71,7 +71,7 @@ public sealed class Data
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
|
@ -1,7 +1,16 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public sealed class DataApp
|
||||
public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// The default constructor for the JSON deserializer.
|
||||
/// </summary>
|
||||
public DataApp() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The language behavior.
|
||||
/// </summary>
|
||||
@ -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.
|
||||
/// </summary>
|
||||
public bool IsSavingEnergy { get; set; }
|
||||
public bool IsSavingEnergy { get; set; } = ManagedConfiguration.Register(configSelection, n => n.IsSavingEnergy, false);
|
||||
|
||||
/// <summary>
|
||||
/// Should we enable spellchecking for all input fields?
|
||||
@ -31,7 +40,7 @@ public sealed class DataApp
|
||||
/// <summary>
|
||||
/// If and when we should look for updates.
|
||||
/// </summary>
|
||||
public UpdateBehavior UpdateBehavior { get; set; } = UpdateBehavior.HOURLY;
|
||||
public UpdateBehavior UpdateBehavior { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UpdateBehavior, UpdateBehavior.HOURLY);
|
||||
|
||||
/// <summary>
|
||||
/// The navigation behavior.
|
||||
@ -41,7 +50,7 @@ public sealed class DataApp
|
||||
/// <summary>
|
||||
/// The visibility setting for previews features.
|
||||
/// </summary>
|
||||
public PreviewVisibility PreviewVisibility { get; set; } = PreviewVisibility.NONE;
|
||||
public PreviewVisibility PreviewVisibility { get; set; } = ManagedConfiguration.Register(configSelection, n => n.PreviewVisibility, PreviewVisibility.NONE);
|
||||
|
||||
/// <summary>
|
||||
/// The enabled preview features.
|
||||
@ -66,5 +75,5 @@ public sealed class DataApp
|
||||
/// <summary>
|
||||
/// Should the user be allowed to add providers?
|
||||
/// </summary>
|
||||
public bool AllowUserToAddProvider { get; set; } = true;
|
||||
public bool AllowUserToAddProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddProvider, true);
|
||||
}
|
@ -33,7 +33,7 @@ public sealed class DataV4
|
||||
/// </summary>
|
||||
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();
|
||||
|
||||
|
@ -6,7 +6,7 @@ public static class ExpressionExtensions
|
||||
{
|
||||
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(typeof(ExpressionExtensions));
|
||||
|
||||
public static MemberExpression GetMemberExpression<T>(this Expression<Func<T, object>> expression)
|
||||
public static MemberExpression GetMemberExpression<TIn, TOut>(this Expression<Func<TIn, TOut>> expression)
|
||||
{
|
||||
switch (expression.Body)
|
||||
{
|
||||
|
3
app/MindWork AI Studio/Settings/IConfig.cs
Normal file
3
app/MindWork AI Studio/Settings/IConfig.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public interface IConfig;
|
198
app/MindWork AI Studio/Settings/ManagedConfiguration.cs
Normal file
198
app/MindWork AI Studio/Settings/ManagedConfiguration.cs
Normal file
@ -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<string, IConfig> METADATA = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a configuration setting with a default value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="configSelection">The expression to select the configuration class.</param>
|
||||
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
|
||||
/// <param name="defaultValue">The default value to use when the setting is not configured.</param>
|
||||
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
|
||||
/// <returns>The default value.</returns>
|
||||
public static TValue Register<TClass, TValue>(Expression<Func<Data, TClass>>? configSelection, Expression<Func<TClass, TValue>> 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<TClass, TValue>(configSelection, propertyExpression)
|
||||
{
|
||||
Default = defaultValue,
|
||||
};
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the configuration metadata for a given configuration selection and property expression.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="configSelection">The expression to select the configuration class.</param>
|
||||
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
|
||||
/// <param name="configMeta">The output parameter that will hold the configuration metadata if found.</param>
|
||||
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
|
||||
/// <returns>True if the configuration metadata was found, otherwise false.</returns>
|
||||
public static bool TryGet<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression, out ConfigMeta<TClass, TValue> configMeta)
|
||||
{
|
||||
var configPath = Path(configSelection, propertyExpression);
|
||||
if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta<TClass, TValue> meta)
|
||||
{
|
||||
configMeta = meta;
|
||||
return true;
|
||||
}
|
||||
|
||||
configMeta = new NoConfig<TClass, TValue>(configSelection, propertyExpression)
|
||||
{
|
||||
Default = default!,
|
||||
};
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to process the configuration settings from a Lua table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
|
||||
/// <param name="settings">The Lua table containing the settings to process.</param>
|
||||
/// <param name="configSelection">The expression to select the configuration class.</param>
|
||||
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
|
||||
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
|
||||
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
|
||||
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
|
||||
public static bool TryProcessConfiguration<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> 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<string>(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<string>(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<string>(out var configuredText) ? ((TValue)(object)configuredText, true) : (configMeta.Default, false),
|
||||
bool => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredBoolValue) && configuredBoolValue.TryRead<bool>(out var configuredState) ? ((TValue)(object)configuredState, true) : (configMeta.Default, false),
|
||||
|
||||
int => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredIntValue) && configuredIntValue.TryRead<int>(out var configuredInt) ? ((TValue)(object)configuredInt, true) : (configMeta.Default, false),
|
||||
double => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredDoubleValue) && configuredDoubleValue.TryRead<double>(out var configuredDouble) ? ((TValue)(object)configuredDouble, true) : (configMeta.Default, false),
|
||||
float => settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredFloatValue) && configuredFloatValue.TryRead<float>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="configSelection">The expression to select the configuration class.</param>
|
||||
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
|
||||
/// <param name="availablePlugins">The collection of available plugins to check against.</param>
|
||||
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the property within the configuration class.</typeparam>
|
||||
/// <returns>True if the configuration setting is left over and was reset, otherwise false.</returns>
|
||||
public static bool IsConfigurationLeftOver<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression, IEnumerable<IAvailablePlugin> 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<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> 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;
|
||||
}
|
||||
}
|
12
app/MindWork AI Studio/Settings/NoConfig.cs
Normal file
12
app/MindWork AI Studio/Settings/NoConfig.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public sealed record NoConfig<TClass, TValue> : ConfigMeta<TClass, TValue>
|
||||
{
|
||||
public NoConfig(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression) : base(configSelection, propertyExpression)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public sealed class SettingsLocker
|
||||
{
|
||||
private readonly Dictionary<string, Dictionary<string, Guid>> lockedProperties = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a property of a class to be locked by a specific configuration plugin ID.
|
||||
/// </summary>
|
||||
/// <param name="propertyExpression">The property expression to lock.</param>
|
||||
/// <param name="configurationPluginId">The ID of the configuration plugin that locks the property.</param>
|
||||
/// <typeparam name="T">The type of the class that contains the property.</typeparam>
|
||||
public void Register<T>(Expression<Func<T, object>> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the lock for a property of a class.
|
||||
/// </summary>
|
||||
/// <param name="propertyExpression">The property expression to remove the lock for.</param>
|
||||
/// <typeparam name="T">The type of the class that contains the property.</typeparam>
|
||||
public void Remove<T>(Expression<Func<T, object>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration plugin ID that locks a specific property of a class.
|
||||
/// </summary>
|
||||
/// <param name="propertyExpression"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public Guid GetConfigurationPluginId<T>(Expression<Func<T, object>> 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<T>(Expression<Func<T, object>> 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);
|
||||
}
|
||||
}
|
@ -349,7 +349,7 @@ public sealed class SettingsManager
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToSettingName<T>(Expression<Func<T, object>> propertyExpression)
|
||||
public static string ToSettingName<TIn, TOut>(Expression<Func<TIn, TOut>> 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}";
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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<PluginConfiguration> LOGGER = Program.LOGGER_FACTORY.CreateLogger<PluginConfiguration>();
|
||||
private static readonly SettingsLocker SETTINGS_LOCKER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsLocker>();
|
||||
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
|
||||
|
||||
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<bool>(null, Event.CONFIGURATION_CHANGED);
|
||||
|
||||
if (!dryRun)
|
||||
{
|
||||
await SETTINGS_MANAGER.StoreSettings();
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(null, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to initialize the UI text content of the plugin.
|
||||
/// </summary>
|
||||
/// <param name="dryRun">When true, the method will not apply any changes, but only check if the configuration can be read.</param>
|
||||
/// <param name="message">The error message, when the UI text content could not be read.</param>
|
||||
/// <returns>True, when the UI text content could be read successfully.</returns>
|
||||
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<LuaTable>(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<LuaTable>(out var settingsTable))
|
||||
{
|
||||
message = TB("The SETTINGS table does not exist or is not a valid table.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settingsTable.TryGetValue(SettingsManager.ToSettingName<DataApp>(x => x.UpdateBehavior), out var updateBehaviorValue) && updateBehaviorValue.TryRead<string>(out var updateBehaviorText) && Enum.TryParse<UpdateBehavior>(updateBehaviorText, true, out var updateBehavior))
|
||||
{
|
||||
SETTINGS_LOCKER.Register<DataApp>(x => x.UpdateBehavior, this.Id);
|
||||
SETTINGS_MANAGER.ConfigurationData.App.UpdateBehavior = updateBehavior;
|
||||
}
|
||||
|
||||
if (settingsTable.TryGetValue(SettingsManager.ToSettingName<DataApp>(x => x.AllowUserToAddProvider), out var dontAllowUserToAddProviderValue) && dontAllowUserToAddProviderValue.TryRead<bool>(out var dontAllowUserToAddProviderEntry))
|
||||
{
|
||||
SETTINGS_LOCKER.Register<DataApp>(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
|
||||
|
@ -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<DataApp>(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<DataApp>(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<DataApp, UpdateBehavior>(x => x.App, x => x.UpdateBehavior, AVAILABLE_PLUGINS))
|
||||
wasConfigurationChanged = true;
|
||||
|
||||
// Allow the user to add providers?
|
||||
if(ManagedConfiguration.IsConfigurationLeftOver<DataApp, bool>(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:
|
||||
|
@ -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;
|
||||
|
@ -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<SettingsManager>();
|
||||
private static readonly SettingsLocker SETTINGS_LOCKER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsLocker>();
|
||||
|
||||
private static bool IS_INITIALIZED;
|
||||
private static string DATA_DIR = string.Empty;
|
||||
|
Loading…
Reference in New Issue
Block a user