Refactored configuration objects (#538)
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2025-08-26 10:59:56 +02:00
1 parent be11efed67
commit c1826426d9
31 files changed
+723 -472

No files matched your search

@@ -0,0 +1,23 @@
namespace AIStudio.Tools.PluginSystem;
public abstract record ConfigurationBaseObject : IConfigurationObject
{
#region Implementation of IConfigurationObject
/// <inheritdoc />
public abstract string Id { get; init; }
/// <inheritdoc />
public abstract uint Num { get; init; }
/// <inheritdoc />
public abstract string Name { get; init; }
/// <inheritdoc />
public abstract bool IsEnterpriseConfiguration { get; init; }
/// <inheritdoc />
public abstract Guid EnterpriseConfigurationPluginId { get; init; }
#endregion
}
@@ -0,0 +1,32 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Represents a configuration object, such as a chat template or a LLM provider.
/// </summary>
public interface IConfigurationObject
{
/// <summary>
/// The unique ID of the configuration object.
/// </summary>
public string Id { get; }
/// <summary>
/// The continuous number of the configuration object.
/// </summary>
public uint Num { get; }
/// <summary>
/// The name of the configuration object.
/// </summary>
public string Name { get; }
/// <summary>
/// Is this configuration object an enterprise configuration?
/// </summary>
public bool IsEnterpriseConfiguration { get; }
/// <summary>
/// The ID of the enterprise configuration plugin.
/// </summary>
public Guid EnterpriseConfigurationPluginId { get; }
}
@@ -0,0 +1,26 @@
namespace AIStudio.Tools.PluginSystem;
public sealed record NoConfigurationObject : ConfigurationBaseObject
{
public static readonly NoConfigurationObject INSTANCE = new();
private NoConfigurationObject()
{
this.Id = Guid.Empty.ToString();
this.Name = "No Configuration";
}
#region Overrides of ConfigurationBaseObject
public override string Id { get; init; }
public override uint Num { get; init; }
public override string Name { get; init; }
public override bool IsEnterpriseConfiguration { get; init; }
public override Guid EnterpriseConfigurationPluginId { get; init; }
#endregion
}
@@ -1,21 +1,15 @@
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Chat;
using Lua;
using Host = AIStudio.Provider.SelfHosted.Host;
using Model = AIStudio.Provider.Model;
namespace AIStudio.Tools.PluginSystem;
public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginType type) : PluginBase(isInternal, state, type)
{
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 SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
private readonly List<PluginConfigurationObject> configObjects = [];
private List<PluginConfigurationObject> configObjects = [];
/// <summary>
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
@@ -51,317 +45,34 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
return false;
}
//
// ===========================================
// Configured settings
// ===========================================
//
// Check for the main SETTINGS table:
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;
}
// Check for updates, and if so, how often?
// Config: 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?
// Config: allow the user to add providers?
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun);
//
// Configured providers:
//
if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead<LuaTable>(out var providersTable))
// Handle configured LLM providers:
if (!PluginConfigurationObject.TryParse(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, x => x.NextProviderNum, mainTable, this.Id, ref this.configObjects, dryRun))
{
message = TB("The LLM_PROVIDERS table does not exist or is not a valid table.");
message = TB("At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist.");
return false;
}
// Handle configured chat templates:
if (!PluginConfigurationObject.TryParse(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, x => x.NextChatTemplateNum, mainTable, this.Id, ref this.configObjects, dryRun))
{
message = TB("At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist.");
return false;
}
message = string.Empty;
var numberProviders = providersTable.ArrayLength;
var configuredProviders = new List<Settings.Provider>(numberProviders);
for (var i = 1; i <= numberProviders; i++)
{
var providerLuaTableValue = providersTable[i];
if (!providerLuaTableValue.TryRead<LuaTable>(out var providerLuaTable))
{
LOGGER.LogWarning($"The LLM_PROVIDERS table at index {i} is not a valid table.");
continue;
}
if(this.TryReadProviderTable(i, providerLuaTable, out var provider))
configuredProviders.Add(provider);
else
LOGGER.LogWarning($"The LLM_PROVIDERS table at index {i} does not contain a valid provider configuration.");
}
//
// Apply the configured providers to the system settings:
//
#pragma warning disable MWAIS0001
foreach (var configuredProvider in configuredProviders)
{
// The iterating variable is immutable, so we need to create a local copy:
var provider = configuredProvider;
// Store this provider in the config object list:
this.configObjects.Add(new()
{
ConfigPluginId = this.Id,
Id = Guid.Parse(provider.Id),
Type = PluginConfigurationObjectType.LLM_PROVIDER,
});
if (dryRun)
continue;
var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id);
if (providerIndex > -1)
{
// Case: The provider already exists, we update it:
var existingProvider = SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex];
provider = provider with { Num = existingProvider.Num }; // Keep the original number
SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex] = provider;
}
else
{
// Case: The provider does not exist, we add it:
provider = provider with { Num = SETTINGS_MANAGER.ConfigurationData.NextProviderNum++ };
SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider);
}
}
#pragma warning restore MWAIS0001
//
// Configured chat templates:
//
if (mainTable.TryGetValue("CHAT_TEMPLATES", out var templatesValue) && templatesValue.TryRead<LuaTable>(out var templatesTable))
{
var numberTemplates = templatesTable.ArrayLength;
var configuredTemplates = new List<ChatTemplate>(numberTemplates);
for (var i = 1; i <= numberTemplates; i++)
{
var templateLuaTableValue = templatesTable[i];
if (!templateLuaTableValue.TryRead<LuaTable>(out var templateLuaTable))
{
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} is not a valid table.");
continue;
}
if(this.TryReadChatTemplateTable(i, templateLuaTable, out var template) && template != ChatTemplate.NO_CHAT_TEMPLATE)
configuredTemplates.Add(template);
else
LOGGER.LogWarning($"The CHAT_TEMPLATES table at index {i} does not contain a valid chat template configuration.");
}
// Apply configured chat templates to the system settings:
foreach (var configuredTemplate in configuredTemplates)
{
// The iterating variable is immutable, so we need to create a local copy:
var template = configuredTemplate;
// Store this provider in the config object list:
this.configObjects.Add(new()
{
ConfigPluginId = this.Id,
Id = Guid.Parse(template.Id),
Type = PluginConfigurationObjectType.CHAT_TEMPLATE,
});
if (dryRun)
continue;
var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id);
if (tplIndex > -1)
{
// Case: The template already exists, we update it:
var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex];
template = template with { Num = existingTemplate.Num };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template;
}
else
{
// Case: The template does not exist, we add it:
template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ };
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template);
}
}
}
return true;
}
private bool TryReadProviderTable(int idx, LuaTable table, out Settings.Provider provider)
{
provider = default;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
return false;
}
if (!table.TryGetValue("InstanceName", out var instanceNameValue) || !instanceNameValue.TryRead<string>(out var instanceName))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid instance name.");
return false;
}
if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(usedLLMProviderText, true, out var usedLLMProvider))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid LLM provider enum value.");
return false;
}
if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead<string>(out var hostText) || !Enum.TryParse<Host>(hostText, true, out var host))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid host enum value.");
return false;
}
if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead<string>(out var hostname))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid hostname.");
return false;
}
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model table.");
return false;
}
if (!this.TryReadModelTable(idx, modelTable, out var model))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model configuration.");
return false;
}
provider = new()
{
Num = 0,
Id = id.ToString(),
InstanceName = instanceName,
UsedLLMProvider = usedLLMProvider,
Model = model,
IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = this.Id,
Hostname = hostname,
Host = host
};
return true;
}
private bool TryReadModelTable(int idx, LuaTable table, out Model model)
{
model = default;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var id))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model ID.");
return false;
}
if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead<string>(out var displayName))
{
LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model display name.");
return false;
}
model = new(id, displayName);
return true;
}
private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate template)
{
template = ChatTemplate.NO_CHAT_TEMPLATE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
{
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid ID. The ID must be a valid GUID.");
return false;
}
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
{
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid name.");
return false;
}
if (!table.TryGetValue("SystemPrompt", out var sysPromptValue) || !sysPromptValue.TryRead<string>(out var systemPrompt))
{
LOGGER.LogWarning($"The configured chat template {idx} does not contain a valid system prompt.");
return false;
}
var predefinedUserPrompt = string.Empty;
if (table.TryGetValue("PredefinedUserPrompt", out var preUserValue) && preUserValue.TryRead<string>(out var preUser))
predefinedUserPrompt = preUser;
var allowProfileUsage = false;
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
allowProfileUsage = allow;
template = new()
{
Num = 0,
Id = id.ToString(),
Name = name,
SystemPrompt = systemPrompt,
PredefinedUserPrompt = predefinedUserPrompt,
ExampleConversation = ParseExampleConversation(idx, table),
AllowProfileUsage = allowProfileUsage,
IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = this.Id
};
return true;
}
private static List<ContentBlock> ParseExampleConversation(int idx, LuaTable table)
{
var exampleConversation = new List<ContentBlock>();
if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead<LuaTable>(out var exConvTable))
return exampleConversation;
var numBlocks = exConvTable.ArrayLength;
for (var j = 1; j <= numBlocks; j++)
{
var blockValue = exConvTable[j];
if (!blockValue.TryRead<LuaTable>(out var blockTable))
{
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} is not a valid table.");
continue;
}
if (!blockTable.TryGetValue("Role", out var roleValue) || !roleValue.TryRead<string>(out var roleText) || !Enum.TryParse<ChatRole>(roleText, true, out var parsedRole))
{
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid role.");
continue;
}
if (!blockTable.TryGetValue("Content", out var contentValue) || !contentValue.TryRead<string>(out var content))
{
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} does not contain a valid content message.");
continue;
}
if (string.IsNullOrWhiteSpace(content))
{
LOGGER.LogWarning($"The ExampleConversation entry {j} in chat template {idx} contains an empty content message.");
continue;
}
exampleConversation.Add(new ContentBlock
{
Time = DateTimeOffset.UtcNow,
Role = parsedRole,
Content = new ContentText { Text = content },
ContentType = ContentType.TEXT,
HideFromUser = true,
});
}
return exampleConversation;
}
}
@@ -1,3 +1,10 @@
using System.Linq.Expressions;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using Lua;
namespace AIStudio.Tools.PluginSystem;
/// <summary>
@@ -6,6 +13,9 @@ namespace AIStudio.Tools.PluginSystem;
/// </summary>
public sealed record PluginConfigurationObject
{
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<PluginConfigurationObject>();
/// <summary>
/// The id of the configuration plugin to which this configuration object belongs.
/// </summary>
@@ -20,4 +30,178 @@ public sealed record PluginConfigurationObject
/// The type of the configuration object.
/// </summary>
public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE;
/// <summary>
/// Parses Lua table entries into configuration objects of the specified type, populating the
/// provided list with results.
/// </summary>
/// <typeparam name="TClass">The type of configuration object to parse, which must
/// inherit from <see cref="ConfigurationBaseObject"/>.</typeparam>
/// <param name="configObjectType">The type of configuration object to process, as specified
/// in <see cref="PluginConfigurationObjectType"/>.</param>
/// <param name="configObjectSelection">An expression to retrieve existing configuration objects from
/// the main configuration data.</param>
/// <param name="nextConfigObjectNumSelection">An expression to retrieve the next available configuration
/// object number from the main configuration data.</param>
/// <param name="mainTable">The Lua table containing entries to parse into configuration objects.</param>
/// <param name="configPluginId">The unique identifier of the plugin associated with the configuration
/// objects being parsed.</param>
/// <param name="configObjects">The list to populate with the parsed configuration objects.
/// This parameter is passed by reference.</param>
/// <param name="dryRun">Specifies whether to perform the operation as a dry run, where changes
/// are not persisted.</param>
/// <returns>Returns true if parsing succeeds and configuration objects are added
/// to the list; otherwise, false.</returns>
public static bool TryParse<TClass>(
PluginConfigurationObjectType configObjectType,
Expression<Func<Data, List<TClass>>> configObjectSelection,
Expression<Func<Data, uint>> nextConfigObjectNumSelection,
LuaTable mainTable,
Guid configPluginId,
ref List<PluginConfigurationObject> configObjects,
bool dryRun
) where TClass : ConfigurationBaseObject
{
var luaTableName = configObjectType switch
{
PluginConfigurationObjectType.LLM_PROVIDER => "LLM_PROVIDERS",
PluginConfigurationObjectType.CHAT_TEMPLATE => "CHAT_TEMPLATES",
PluginConfigurationObjectType.DATA_SOURCE => "DATA_SOURCES",
PluginConfigurationObjectType.EMBEDDING_PROVIDER => "EMBEDDING_PROVIDERS",
PluginConfigurationObjectType.PROFILE => "PROFILES",
_ => null,
};
if (luaTableName is null)
{
LOG.LogError($"The configuration object type '{configObjectType}' is not supported yet.");
return false;
}
if (!mainTable.TryGetValue(luaTableName, out var luaValue) || !luaValue.TryRead<LuaTable>(out var luaTable))
{
LOG.LogWarning($"The {luaTableName} table does not exist or is not a valid table.");
return false;
}
var storedObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData);
var numberObjects = luaTable.ArrayLength;
ThreadSafeRandom? random = null;
for (var i = 1; i <= numberObjects; i++)
{
var luaObjectTableValue = luaTable[i];
if (!luaObjectTableValue.TryRead<LuaTable>(out var luaObjectTable))
{
LOG.LogWarning($"The {luaObjectTable} table at index {i} is not a valid table.");
continue;
}
var (wasParsingSuccessful, configObject) = configObjectType switch
{
PluginConfigurationObjectType.LLM_PROVIDER => (Settings.Provider.TryParseProviderTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != Settings.Provider.NONE, configurationObject),
PluginConfigurationObjectType.CHAT_TEMPLATE => (ChatTemplate.TryParseChatTemplateTable(i, luaObjectTable, configPluginId, out var configurationObject) && configurationObject != ChatTemplate.NO_CHAT_TEMPLATE, configurationObject),
_ => (false, NoConfigurationObject.INSTANCE)
};
if (wasParsingSuccessful)
{
// Store it in the config object list:
configObjects.Add(new()
{
ConfigPluginId = configPluginId,
Id = Guid.Parse(configObject.Id),
Type = configObjectType,
});
if (dryRun)
continue;
var objectIndex = storedObjects.FindIndex(t => t.Id == configObject.Id);
// Case: The object already exists, we update it:
if (objectIndex > -1)
{
var existingObject = storedObjects[objectIndex];
configObject = configObject with { Num = existingObject.Num };
storedObjects[objectIndex] = (TClass)configObject;
}
// Case: The object does not exist, we have to add it
else
{
if (nextConfigObjectNumSelection.TryIncrement(SETTINGS_MANAGER.ConfigurationData, IncrementType.POST) is { Success: true, UpdatedValue: var nextNum })
{
// Case: Increment the next number was successful
configObject = configObject with { Num = nextNum };
storedObjects.Add((TClass)configObject);
}
else
{
// Case: The next number could not be incremented, we use a random number
random ??= new ThreadSafeRandom();
configObject = configObject with { Num = (uint)random.Next(500_000, 1_000_000) };
storedObjects.Add((TClass)configObject);
LOG.LogWarning($"The next number for the configuration object '{configObject.Name}' (id={configObject.Id}) could not be incremented. Using a random number instead.");
}
}
}
else
LOG.LogWarning($"The {luaObjectTable} table at index {i} does not contain a valid chat template configuration.");
}
return true;
}
/// <summary>
/// Cleans up configuration objects of a specified type that are no longer associated with any available plugin.
/// </summary>
/// <typeparam name="TClass">The type of configuration object to clean up.</typeparam>
/// <param name="configObjectType">The type of configuration object to process.</param>
/// <param name="configObjectSelection">A selection expression to retrieve the configuration objects from the main configuration.</param>
/// <param name="availablePlugins">A list of currently available plugins.</param>
/// <param name="configObjectList">A list of all existing configuration objects.</param>
/// <returns>Returns true if the configuration was altered during cleanup; otherwise, false.</returns>
public static bool CleanLeftOverConfigurationObjects<TClass>(
PluginConfigurationObjectType configObjectType,
Expression<Func<Data, List<TClass>>> configObjectSelection,
IList<IAvailablePlugin> availablePlugins,
IList<PluginConfigurationObject> configObjectList) where TClass : IConfigurationObject
{
var configuredObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData);
var leftOverObjects = new List<TClass>();
foreach (var configuredObject in configuredObjects)
{
if(!configuredObject.IsEnterpriseConfiguration)
continue;
var configObjectSourcePluginId = configuredObject.EnterpriseConfigurationPluginId;
if(configObjectSourcePluginId == Guid.Empty)
continue;
var templateSourcePlugin = availablePlugins.FirstOrDefault(plugin => plugin.Id == configObjectSourcePluginId);
if(templateSourcePlugin is null)
{
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is based on a plugin that is not available anymore. Removing the chat template from the settings.");
leftOverObjects.Add(configuredObject);
}
if(!configObjectList.Any(configObject =>
configObject.Type == configObjectType &&
configObject.ConfigPluginId == configObjectSourcePluginId &&
configObject.Id.ToString() == configuredObject.Id))
{
LOG.LogWarning($"The configured object '{configuredObject.Name}' (id={configuredObject.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings.");
leftOverObjects.Add(configuredObject);
}
}
// Remove collected items after enumeration to avoid modifying the collection during iteration:
var wasConfigurationChanged = leftOverObjects.Count > 0;
foreach (var item in leftOverObjects.Distinct())
configuredObjects.Remove(item);
return wasConfigurationChanged;
}
}
@@ -92,10 +92,10 @@ public static partial class PluginFactory
case { IsValid: false }:
LOG.LogError($"Was not able to load plugin '{pluginMainFile}', because the Lua code is not a valid AI Studio plugin. There are {plugin.Issues.Count()} issues to fix. First issue is: {plugin.Issues.FirstOrDefault()}");
#if DEBUG
#if DEBUG
foreach (var pluginIssue in plugin.Issues)
LOG.LogError($"Plugin issue: {pluginIssue}");
#endif
#endif
continue;
case { IsMaintained: false }:
@@ -125,89 +125,24 @@ 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:
// 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;
//
// Check LLM providers:
//
#pragma warning disable MWAIS0001
var configuredProviders = SETTINGS_MANAGER.ConfigurationData.Providers.ToList();
foreach (var configuredProvider in configuredProviders)
{
if(!configuredProvider.IsEnterpriseConfiguration)
continue;
var providerSourcePluginId = configuredProvider.EnterpriseConfigurationPluginId;
if(providerSourcePluginId == Guid.Empty)
continue;
var providerSourcePlugin = AVAILABLE_PLUGINS.FirstOrDefault(plugin => plugin.Id == providerSourcePluginId);
if(providerSourcePlugin is null)
{
LOG.LogWarning($"The configured LLM provider '{configuredProvider.InstanceName}' (id={configuredProvider.Id}) is based on a plugin that is not available anymore. Removing the provider from the settings.");
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
wasConfigurationChanged = true;
}
if(!configObjectList.Any(configObject =>
configObject.Type is PluginConfigurationObjectType.LLM_PROVIDER &&
configObject.ConfigPluginId == providerSourcePluginId &&
configObject.Id.ToString() == configuredProvider.Id))
{
LOG.LogWarning($"The configured LLM provider '{configuredProvider.InstanceName}' (id={configuredProvider.Id}) is not present in the configuration plugin anymore. Removing the provider from the settings.");
SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider);
wasConfigurationChanged = true;
}
}
#pragma warning restore MWAIS0001
var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList);
//
// Check chat templates:
//
var configuredTemplates = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.ToList();
foreach (var configuredTemplate in configuredTemplates)
{
if(!configuredTemplate.IsEnterpriseConfiguration)
continue;
var templateSourcePluginId = configuredTemplate.EnterpriseConfigurationPluginId;
if(templateSourcePluginId == Guid.Empty)
continue;
var templateSourcePlugin = AVAILABLE_PLUGINS.FirstOrDefault(plugin => plugin.Id == templateSourcePluginId);
if(templateSourcePlugin is null)
{
LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is based on a plugin that is not available anymore. Removing the chat template from the settings.");
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
wasConfigurationChanged = true;
}
if(!configObjectList.Any(configObject =>
configObject.Type is PluginConfigurationObjectType.CHAT_TEMPLATE &&
configObject.ConfigPluginId == templateSourcePluginId &&
configObject.Id.ToString() == configuredTemplate.Id))
{
LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings.");
SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate);
wasConfigurationChanged = true;
}
}
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
//
// ==========================================================
// Check all possible settings:
// ==========================================================
//
// Check for updates, and if so, how often?
// Check for update behavior:
if(ManagedConfiguration.IsConfigurationLeftOver<DataApp, UpdateBehavior>(x => x.App, x => x.UpdateBehavior, AVAILABLE_PLUGINS))
wasConfigurationChanged = true;
// Allow the user to add providers?
// Check for users allowed to added providers:
if(ManagedConfiguration.IsConfigurationLeftOver<DataApp, bool>(x => x.App, x => x.AllowUserToAddProvider, AVAILABLE_PLUGINS))
wasConfigurationChanged = true;