From c9415b01b4b48931e3ffbc6c1387b2293125a3e6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 21 Aug 2025 16:27:45 +0200 Subject: [PATCH] Refactor plugin configuration to streamline handling and parsing of objects --- .../Settings/ChatTemplate.cs | 98 +++++- app/MindWork AI Studio/Settings/Provider.cs | 92 ++++- .../PluginSystem/ConfigurationBaseObject.cs | 23 ++ .../PluginSystem/NoConfigurationObject.cs | 26 ++ .../Tools/PluginSystem/PluginConfiguration.cs | 321 +----------------- .../PluginSystem/PluginConfigurationObject.cs | 125 +++++++ 6 files changed, 377 insertions(+), 308 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ConfigurationBaseObject.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/NoConfigurationObject.cs diff --git a/app/MindWork AI Studio/Settings/ChatTemplate.cs b/app/MindWork AI Studio/Settings/ChatTemplate.cs index de1f5e9b..38f3623d 100644 --- a/app/MindWork AI Studio/Settings/ChatTemplate.cs +++ b/app/MindWork AI Studio/Settings/ChatTemplate.cs @@ -1,6 +1,8 @@ using AIStudio.Chat; using AIStudio.Tools.PluginSystem; +using Lua; + namespace AIStudio.Settings; public record ChatTemplate( @@ -12,7 +14,7 @@ public record ChatTemplate( List ExampleConversation, bool AllowProfileUsage, bool IsEnterpriseConfiguration = false, - Guid EnterpriseConfigurationPluginId = default) : IConfigurationObject + Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject { public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false) { @@ -20,6 +22,8 @@ public record ChatTemplate( private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate)); + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + public static readonly ChatTemplate NO_CHAT_TEMPLATE = new() { Name = TB("Use no chat template"), @@ -50,4 +54,96 @@ public record ChatTemplate( return this.SystemPrompt; } + + public static bool TryParseChatTemplateTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject template) + { + template = NO_CHAT_TEMPLATE; + if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(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(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(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(out var preUser)) + predefinedUserPrompt = preUser; + + var allowProfileUsage = false; + if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead(out var allow)) + allowProfileUsage = allow; + + template = new ChatTemplate + { + Num = 0, + Id = id.ToString(), + Name = name, + SystemPrompt = systemPrompt, + PredefinedUserPrompt = predefinedUserPrompt, + ExampleConversation = ParseExampleConversation(idx, table), + AllowProfileUsage = allowProfileUsage, + IsEnterpriseConfiguration = true, + EnterpriseConfigurationPluginId = configPluginId, + }; + + return true; + } + + private static List ParseExampleConversation(int idx, LuaTable table) + { + var exampleConversation = new List(); + if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead(out var exConvTable)) + return exampleConversation; + + var numBlocks = exConvTable.ArrayLength; + for (var j = 1; j <= numBlocks; j++) + { + var blockValue = exConvTable[j]; + if (!blockValue.TryRead(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(out var roleText) || !Enum.TryParse(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(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; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs index f6da48ab..36568e63 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -4,6 +4,8 @@ using AIStudio.Provider; using AIStudio.Provider.HuggingFace; using AIStudio.Tools.PluginSystem; +using Lua; + using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Settings; @@ -29,8 +31,9 @@ public record Provider( Guid EnterpriseConfigurationPluginId = default, string Hostname = "http://localhost:1234", Host Host = Host.NONE, - HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ISecretId, IConfigurationObject + HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ConfigurationBaseObject, ISecretId { + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); public static readonly Provider NONE = new(); @@ -77,7 +80,92 @@ public record Provider( #region Implementation of IConfigurationObject - public string Name => this.InstanceName; + public override string Name + { + get => this.InstanceName; + init => this.InstanceName = value; + } #endregion + + public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider) + { + provider = NONE; + if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(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(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(out var usedLLMProviderText) || !Enum.TryParse(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(out var hostText) || !Enum.TryParse(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(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(out var modelTable)) + { + LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model table."); + return false; + } + + if (!TryReadModelTable(idx, modelTable, out var model)) + { + LOGGER.LogWarning($"The configured provider {idx} does not contain a valid model configuration."); + return false; + } + + provider = new Provider + { + Num = 0, + Id = id.ToString(), + InstanceName = instanceName, + UsedLLMProvider = usedLLMProvider, + Model = model, + IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED, + IsEnterpriseConfiguration = true, + EnterpriseConfigurationPluginId = configPluginId, + Hostname = hostname, + Host = host + }; + + return true; + } + + private static bool TryReadModelTable(int idx, LuaTable table, out Model model) + { + model = default; + if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(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(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; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationBaseObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationBaseObject.cs new file mode 100644 index 00000000..97d48d61 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ConfigurationBaseObject.cs @@ -0,0 +1,23 @@ +namespace AIStudio.Tools.PluginSystem; + +public abstract record ConfigurationBaseObject : IConfigurationObject +{ + #region Implementation of IConfigurationObject + + /// + public abstract string Id { get; init; } + + /// + public abstract uint Num { get; init; } + + /// + public abstract string Name { get; init; } + + /// + public abstract bool IsEnterpriseConfiguration { get; init; } + + /// + public abstract Guid EnterpriseConfigurationPluginId { get; init; } + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoConfigurationObject.cs new file mode 100644 index 00000000..d42cb6f7 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoConfigurationObject.cs @@ -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 +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 9e309b10..5e5efd3e 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -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 LOGGER = Program.LOGGER_FACTORY.CreateLogger(); private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); - private readonly List configObjects = []; + private List configObjects = []; /// /// 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(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(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(numberProviders); - for (var i = 1; i <= numberProviders; i++) - { - var providerLuaTableValue = providersTable[i]; - if (!providerLuaTableValue.TryRead(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(out var templatesTable)) - { - var numberTemplates = templatesTable.ArrayLength; - var configuredTemplates = new List(numberTemplates); - for (var i = 1; i <= numberTemplates; i++) - { - var templateLuaTableValue = templatesTable[i]; - if (!templateLuaTableValue.TryRead(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(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(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(out var usedLLMProviderText) || !Enum.TryParse(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(out var hostText) || !Enum.TryParse(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(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(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(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(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(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(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(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(out var preUser)) - predefinedUserPrompt = preUser; - - var allowProfileUsage = false; - if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead(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 ParseExampleConversation(int idx, LuaTable table) - { - var exampleConversation = new List(); - if (!table.TryGetValue("ExampleConversation", out var exConvValue) || !exConvValue.TryRead(out var exConvTable)) - return exampleConversation; - - var numBlocks = exConvTable.ArrayLength; - for (var j = 1; j <= numBlocks; j++) - { - var blockValue = exConvTable[j]; - if (!blockValue.TryRead(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(out var roleText) || !Enum.TryParse(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(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; - } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs index d7d4f5f2..60704414 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -3,6 +3,8 @@ using System.Linq.Expressions; using AIStudio.Settings; using AIStudio.Settings.DataModel; +using Lua; + namespace AIStudio.Tools.PluginSystem; /// @@ -29,6 +31,129 @@ public sealed record PluginConfigurationObject /// public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE; + /// + /// Parses Lua table entries into configuration objects of the specified type, populating the + /// provided list with results. + /// + /// The type of configuration object to parse, which must + /// inherit from . + /// The type of configuration object to process, as specified + /// in . + /// An expression to retrieve existing configuration objects from + /// the main configuration data. + /// An expression to retrieve the next available configuration + /// object number from the main configuration data. + /// The Lua table containing entries to parse into configuration objects. + /// The unique identifier of the plugin associated with the configuration + /// objects being parsed. + /// The list to populate with the parsed configuration objects. + /// This parameter is passed by reference. + /// Specifies whether to perform the operation as a dry run, where changes + /// are not persisted. + /// Returns true if parsing succeeds and configuration objects are added + /// to the list; otherwise, false. + public static bool TryParse( + PluginConfigurationObjectType configObjectType, + Expression>> configObjectSelection, + Expression> nextConfigObjectNumSelection, + LuaTable mainTable, + Guid configPluginId, + ref List 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(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(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 + { + // Case: Increment the next number was successful + if (nextConfigObjectNumSelection.TryIncrement(SETTINGS_MANAGER.ConfigurationData) is { Success: true, UpdatedValue: var nextNum }) + { + 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; + } + /// /// Cleans up configuration objects of a specified type that are no longer associated with any available plugin. ///