From afefc651a542b8e952b29efc369822d07f197c7b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Aug 2025 10:10:34 +0200 Subject: [PATCH 01/14] Made ChatTemplate a reference type --- app/MindWork AI Studio/Settings/ChatTemplate.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/Settings/ChatTemplate.cs b/app/MindWork AI Studio/Settings/ChatTemplate.cs index 02e0061c..cd25794d 100644 --- a/app/MindWork AI Studio/Settings/ChatTemplate.cs +++ b/app/MindWork AI Studio/Settings/ChatTemplate.cs @@ -3,8 +3,12 @@ namespace AIStudio.Settings; -public readonly record struct ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List ExampleConversation, bool AllowProfileUsage) +public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List ExampleConversation, bool AllowProfileUsage) { + public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false) + { + } + private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate)); public static readonly ChatTemplate NO_CHAT_TEMPLATE = new() From 26e597b1596ae3858e19159852dbef6ccf3ca9c5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Aug 2025 22:21:32 +0200 Subject: [PATCH 02/14] Allow chat templates coming from a config plugin --- app/MindWork AI Studio/Chat/ChatThread.cs | 2 +- .../Components/ChatComponent.razor.cs | 13 +-- .../Settings/SettingsPanelProviders.razor.cs | 3 + .../Dialogs/ChatTemplateDialog.razor.cs | 3 + .../SettingsDialogChatTemplate.razor.cs | 3 + .../Settings/ChatTemplate.cs | 4 +- .../Settings/SettingsManager.cs | 4 +- .../Tools/ComponentsExtensions.cs | 4 +- .../Tools/PluginSystem/PluginConfiguration.cs | 88 +++++++++++++++++++ .../PluginSystem/PluginFactory.Loading.cs | 22 +++++ 10 files changed, 134 insertions(+), 12 deletions(-) diff --git a/app/MindWork AI Studio/Chat/ChatThread.cs b/app/MindWork AI Studio/Chat/ChatThread.cs index d38ddad2..0193ce28 100644 --- a/app/MindWork AI Studio/Chat/ChatThread.cs +++ b/app/MindWork AI Studio/Chat/ChatThread.cs @@ -109,7 +109,7 @@ public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread ch else { var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate); - if(chatTemplate == default) + if(chatTemplate == null) systemPromptTextWithChatTemplate = chatThread.SystemPrompt; else { diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 43f79a10..3c4a8d38 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -327,7 +327,9 @@ private async Task ProfileWasChanged(Profile profile) private async Task ChatTemplateWasChanged(ChatTemplate chatTemplate) { this.currentChatTemplate = chatTemplate; - this.userInput = this.currentChatTemplate.PredefinedUserPrompt; + if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt)) + this.userInput = this.currentChatTemplate.PredefinedUserPrompt; + if(this.ChatThread is null) return; @@ -435,7 +437,7 @@ private async Task SendMessage(bool reuseLastUserPrompt = false) DataSourceOptions = this.earlyDataSourceOptions, Name = this.ExtractThreadName(this.userInput), Seed = this.RNG.Next(), - Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(), + Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(), }; await this.ChatThreadChanged.InvokeAsync(this.ChatThread); @@ -673,7 +675,7 @@ private async Task StartNewChat(bool useSameWorkspace = false, bool deletePrevio ChatId = Guid.NewGuid(), Name = string.Empty, Seed = this.RNG.Next(), - Blocks = this.currentChatTemplate == default ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(), + Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(), }; } @@ -813,9 +815,8 @@ private async Task SelectProviderWhenLoadingChat() // Try to select the chat template: if (!string.IsNullOrWhiteSpace(chatChatTemplate)) { - this.currentChatTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate); - if(this.currentChatTemplate == default) - this.currentChatTemplate = ChatTemplate.NO_CHAT_TEMPLATE; + var selectedTemplate = this.SettingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatChatTemplate); + this.currentChatTemplate = selectedTemplate ?? ChatTemplate.NO_CHAT_TEMPLATE; } } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index 16ba7727..608dec29 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -54,6 +54,9 @@ private async Task AddLLMProvider() [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")] private async Task EditLLMProvider(AIStudio.Settings.Provider provider) { + if (provider.IsEnterpriseConfiguration) + return; + var dialogParameters = new DialogParameters { { x => x.DataNum, provider.Num }, diff --git a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs index e95b0d76..3f9378ef 100644 --- a/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ChatTemplateDialog.razor.cs @@ -129,6 +129,9 @@ protected override async Task OnAfterRenderAsync(bool firstRender) PredefinedUserPrompt = this.PredefinedUserPrompt, ExampleConversation = this.dataExampleConversation, AllowProfileUsage = this.AllowProfileUsage, + + EnterpriseConfigurationPluginId = Guid.Empty, + IsEnterpriseConfiguration = false, }; private void RemoveMessage(ContentBlock item) diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs index 364eb49a..73ed5fa5 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs @@ -53,6 +53,9 @@ private async Task AddChatTemplate() private async Task EditChatTemplate(ChatTemplate chatTemplate) { + if (chatTemplate == ChatTemplate.NO_CHAT_TEMPLATE || chatTemplate.IsEnterpriseConfiguration) + return; + var dialogParameters = new DialogParameters { { x => x.DataNum, chatTemplate.Num }, diff --git a/app/MindWork AI Studio/Settings/ChatTemplate.cs b/app/MindWork AI Studio/Settings/ChatTemplate.cs index cd25794d..6842ae00 100644 --- a/app/MindWork AI Studio/Settings/ChatTemplate.cs +++ b/app/MindWork AI Studio/Settings/ChatTemplate.cs @@ -3,7 +3,7 @@ namespace AIStudio.Settings; -public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List ExampleConversation, bool AllowProfileUsage) +public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List ExampleConversation, bool AllowProfileUsage, bool IsEnterpriseConfiguration = false, Guid EnterpriseConfigurationPluginId = default) { public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false) { @@ -20,6 +20,8 @@ public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empt Num = uint.MaxValue, ExampleConversation = [], AllowProfileUsage = true, + EnterpriseConfigurationPluginId = Guid.Empty, + IsEnterpriseConfiguration = false, }; #region Overrides of ValueType diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 059d0f12..7cad25a2 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -270,11 +270,11 @@ public Profile GetPreselectedProfile(Tools.Components component) public ChatTemplate GetPreselectedChatTemplate(Tools.Components component) { var preselection = component.PreselectedChatTemplate(this); - if (preselection != default) + if (preselection != ChatTemplate.NO_CHAT_TEMPLATE) return preselection; preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedChatTemplate); - return preselection != default ? preselection : ChatTemplate.NO_CHAT_TEMPLATE; + return preselection ?? ChatTemplate.NO_CHAT_TEMPLATE; } public ConfidenceLevel GetConfiguredConfidenceLevel(LLMProviders llmProvider) diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index e4bd317c..18ac4f41 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -133,8 +133,8 @@ public static class ComponentsExtensions public static ChatTemplate PreselectedChatTemplate(this Components component, SettingsManager settingsManager) => component switch { - Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) : default, + Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedChatTemplate) ?? ChatTemplate.NO_CHAT_TEMPLATE : ChatTemplate.NO_CHAT_TEMPLATE, - _ => default, + _ => ChatTemplate.NO_CHAT_TEMPLATE, }; } \ 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 7de7137f..df6b09c2 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -111,6 +111,47 @@ private bool TryProcessConfiguration(bool dryRun, out string message) } #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) + { + var template = configuredTemplate; + var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id); + if (tplIndex > -1) + { + var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex]; + template = template with { Num = existingTemplate.Num }; + SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template; + } + else + { + template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ }; + SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template); + } + } + } + return true; } @@ -194,4 +235,51 @@ private bool TryReadModelTable(int idx, LuaTable table, out Model model) 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; + + #warning Need to add support for ExampleConversation + + template = new() + { + Num = 0, + Id = id.ToString(), + Name = name, + SystemPrompt = systemPrompt, + PredefinedUserPrompt = predefinedUserPrompt, + ExampleConversation = [], + AllowProfileUsage = allowProfileUsage, + IsEnterpriseConfiguration = true, + EnterpriseConfigurationPluginId = this.Id + }; + + return true; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 667ed867..98dec636 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -151,7 +151,29 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) } } #pragma warning restore MWAIS0001 + + // + // 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; + } + } + // // ========================================================== // Check all possible settings: From f78f5d475097c3269be650d52598e2a2f8991762 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 16 Aug 2025 22:30:27 +0200 Subject: [PATCH 03/14] Disable actions for enterprise-managed chat templates --- .../Settings/SettingsDialogChatTemplate.razor | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor index a11c9de0..bb1fb816 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor @@ -31,14 +31,23 @@ @context.Num @context.Name - - - - - - - - + @if (context.IsEnterpriseConfiguration) + { + + + + } + else + { + + + + + + + + + } From d6c7bbe10f1a7d7fa5231417f4bf723428054562 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:00:56 +0200 Subject: [PATCH 04/14] Add support for parsing ExampleConversation in plugin configuration --- .../Tools/PluginSystem/PluginConfiguration.cs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index df6b09c2..e4c1dd37 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -1,5 +1,6 @@ using AIStudio.Provider; using AIStudio.Settings; +using AIStudio.Chat; using Lua; @@ -265,8 +266,6 @@ private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead(out var allow)) allowProfileUsage = allow; - #warning Need to add support for ExampleConversation - template = new() { Num = 0, @@ -274,7 +273,7 @@ private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate Name = name, SystemPrompt = systemPrompt, PredefinedUserPrompt = predefinedUserPrompt, - ExampleConversation = [], + ExampleConversation = ParseExampleConversation(idx, table), AllowProfileUsage = allowProfileUsage, IsEnterpriseConfiguration = true, EnterpriseConfigurationPluginId = this.Id @@ -282,4 +281,51 @@ private bool TryReadChatTemplateTable(int idx, LuaTable table, out ChatTemplate 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 From 825b666001a6a5d20b5bb44257c8c1035fa75332 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:28:07 +0200 Subject: [PATCH 05/14] Added a Junie project guidelines document --- .junie/guidelines.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .junie/guidelines.md diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 00000000..f8dfd452 --- /dev/null +++ b/.junie/guidelines.md @@ -0,0 +1,17 @@ +# Project Guidelines + +## Repository Structure +- The repository and the app consist of a Rust project in the `runtime` folder and a .NET solution in the `app` folder. +- The .NET solution then contains 4 .NET projects: + - `Build Script` is not required for running the app; instead, it contains the build script for creating new releases, for example. + - `MindWork AI Studio` contains the actual app code. + - `SharedTools` contains types that are needed in the build script and in the app, for example. + - `SourceCodeRules` is a Roslyn analyzer project. It contains analyzers and code fixes that we use to enforce code style rules within the team. + +## Changelogs +- There is a changelog in Markdown format for each version. +- All changelogs are located in the folder `app/MindWork AI Studio/wwwroot/changelog`. +- These changelogs are intended for end users, not for developers. +- Therefore, we don't mention all changes in the changelog: changes that end users wouldn't understand remain unmentioned. For complex refactorings, for example, we mention a generic point that the code quality has been improved to enhance future maintenance. +- The changelog is always written in US English. +- The changelog doesn't mention bug fixes if the bug was never shipped and users don't know about it. \ No newline at end of file From 021455bdd4988b8b84259cdb7557c870a502b45f Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:40:01 +0200 Subject: [PATCH 06/14] Add example chat template to the example plugin configuration --- .../Plugins/configuration/plugin.lua | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index b81e8686..e7d5005b 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -68,4 +68,28 @@ CONFIG["SETTINGS"] = {} -- Configure the user permission to add providers: -- Allowed values are: true, false --- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false \ No newline at end of file +-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false + +-- Example chat templates for this configuration: +CONFIG["CHAT_TEMPLATES"] = {} + +-- A simple example chat template: +CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = { + ["Id"] = "00000000-0000-0000-0000-000000000000", + ["Name"] = "", + ["SystemPrompt"] = "You are 's helpful AI assistant for .", + ["PredefinedUserPrompt"] = "Please help me with ...", + ["AllowProfileUsage"] = true, + ["ExampleConversation"] = { + { + -- Allowed values are: USER, AI, SYSTEM + ["Role"] = "USER", + ["Content"] = "Hello! Can you help me with a quick task?" + }, + { + -- Allowed values are: USER, AI, SYSTEM + ["Role"] = "AI", + ["Content"] = "Of course. What do you need?" + } + } +} From 05d86f8e3994eb9e5e180d581541016a1cebcc97 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:49:05 +0200 Subject: [PATCH 07/14] Updated changelog --- app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md | 1 + 1 file changed, 1 insertion(+) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md index 8e3eaf85..ad4810f9 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -1,3 +1,4 @@ # v0.9.51, build 226 (2025-08-xx xx:xx UTC) +- Added support for predefined chat templates in configuration plugins to help enterprises roll out consistent templates across the organization. - Improved memory usage in several areas of the app. - Fixed a bug in various assistants where some text fields were not reset when resetting. From 06f3988b18c107990ec63d201828ab205c793ae9 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:54:59 +0200 Subject: [PATCH 08/14] Update SystemPrompt in example chat template configuration --- app/MindWork AI Studio/Plugins/configuration/plugin.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index e7d5005b..5513e016 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -77,7 +77,7 @@ CONFIG["CHAT_TEMPLATES"] = {} CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = { ["Id"] = "00000000-0000-0000-0000-000000000000", ["Name"] = "", - ["SystemPrompt"] = "You are 's helpful AI assistant for .", + ["SystemPrompt"] = "You are 's helpful AI assistant for . Your task is ...", ["PredefinedUserPrompt"] = "Please help me with ...", ["AllowProfileUsage"] = true, ["ExampleConversation"] = { From 6b112c9feebf78d054806abe86ac98e452c2911b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 14:57:48 +0200 Subject: [PATCH 09/14] Updated I18N --- app/MindWork AI Studio/Assistants/I18N/allTexts.lua | 3 +++ .../de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua | 5 ++++- .../en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index ec9a8de8..338057f4 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -3415,6 +3415,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678 -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close" +-- This template is managed by your organization. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization." + -- Edit Chat Template UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template" diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index c79650fb..4777ab01 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -3417,6 +3417,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678 -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Schließen" +-- This template is managed by your organization. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "Diesee Vorlage wird von Ihrer Organisation verwaltet." + -- Edit Chat Template UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Chat-Vorlage bearbeiten" @@ -4584,7 +4587,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Letztes Änderungsproto -- Choose the provider and model best suited for your current task. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Wählen Sie den Anbieter und das Modell aus, die am besten zu ihrer aktuellen Aufgabe passen." --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "Sie sind an keinen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face und selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio, Groq oder Fireworks. Für Wissenschaftler und Mitarbeiter von Forschungseinrichtungen unterstützen wir auch die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Anmeldungen wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen und viele internationale Universitäten verfügbar." -- Quick Start Guide diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 943d3c35..5d546552 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -3417,6 +3417,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T32678 -- Close UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3448155331"] = "Close" +-- This template is managed by your organization. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3576775249"] = "This template is managed by your organization." + -- Edit Chat Template UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHATTEMPLATE::T3596030597"] = "Edit Chat Template" @@ -4584,7 +4587,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog" -- Choose the provider and model best suited for your current task. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Choose the provider and model best suited for your current task." --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2900280782"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." -- Quick Start Guide From f221119d71a34b6c8571e557d75120d1a44dbab1 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 16:29:59 +0200 Subject: [PATCH 10/14] Spelling --- .../Tools/PluginSystem/PluginConfiguration.cs | 4 ++-- .../Tools/PluginSystem/PluginFactory.Loading.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index e4c1dd37..8b8a859c 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -60,7 +60,7 @@ private bool TryProcessConfiguration(bool dryRun, out string message) ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun); // - // Configured providers + // Configured providers: // if (!mainTable.TryGetValue("LLM_PROVIDERS", out var providersValue) || !providersValue.TryRead(out var providersTable)) { @@ -113,7 +113,7 @@ private bool TryProcessConfiguration(bool dryRun, out string message) #pragma warning restore MWAIS0001 // - // Configured chat templates + // Configured chat templates: // if (mainTable.TryGetValue("CHAT_TEMPLATES", out var templatesValue) && templatesValue.TryRead(out var templatesTable)) { diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 98dec636..c92aafaf 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -153,7 +153,7 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) #pragma warning restore MWAIS0001 // - // Check Chat Templates: + // Check chat templates: // var configuredTemplates = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.ToList(); foreach (var configuredTemplate in configuredTemplates) From 3963da136632f68727e6c17138d517f37d9bd4e4 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 16:32:10 +0200 Subject: [PATCH 11/14] Add dryRun checks to provider and chat template configuration logic --- .../Tools/PluginSystem/PluginConfiguration.cs | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 8b8a859c..70799c56 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -90,24 +90,27 @@ private bool TryProcessConfiguration(bool dryRun, out string message) // Apply the configured providers to the system settings: // #pragma warning disable MWAIS0001 - foreach (var configuredProvider in configuredProviders) + if (!dryRun) { - // The iterating variable is immutable, so we need to create a local copy: - var provider = configuredProvider; - - var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id); - if (providerIndex > -1) + foreach (var configuredProvider in configuredProviders) { - // 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); + // The iterating variable is immutable, so we need to create a local copy: + var provider = configuredProvider; + + 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 @@ -135,20 +138,26 @@ private bool TryProcessConfiguration(bool dryRun, out string message) } // Apply configured chat templates to the system settings: - foreach (var configuredTemplate in configuredTemplates) + if (!dryRun) { - var template = configuredTemplate; - var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id); - if (tplIndex > -1) - { - var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex]; - template = template with { Num = existingTemplate.Num }; - SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template; - } - else + foreach (var configuredTemplate in configuredTemplates) { - template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ }; - SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template); + // The iterating variable is immutable, so we need to create a local copy: + var template = configuredTemplate; + 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); + } } } } From f88fdabfbe1b1abc8038ed57d12573cd38a9529d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 17 Aug 2025 21:17:47 +0200 Subject: [PATCH 12/14] Validate and remove stale providers and chat templates from still valid configuration plugins --- .../PluginSystem/PluginFactory.Loading.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index c92aafaf..d9ac6fde 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -174,6 +174,40 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) } } + // + // Before checking simple settings, validate that still-present configuration plugins haven't removed individual + // providers or chat templates they previously managed. If so, remove those items from our settings as well: + // + #pragma warning disable MWAIS0001 + foreach (var runningPlugin in RUNNING_PLUGINS.OfType()) + { + var (providerIds, templateIds) = runningPlugin.GetManagedObjectIds(); + var cfgPluginId = runningPlugin.Id; + + // Providers managed by this plugin but no longer present in plugin config + var providersToRemove = SETTINGS_MANAGER.ConfigurationData.Providers + .Where(p => p.IsEnterpriseConfiguration && p.EnterpriseConfigurationPluginId == cfgPluginId && !providerIds.Contains(p.Id)) + .ToList(); + foreach (var p in providersToRemove) + { + LOG.LogWarning($"The configured LLM provider '{p.InstanceName}' (id={p.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the provider from the settings."); + SETTINGS_MANAGER.ConfigurationData.Providers.Remove(p); + wasConfigurationChanged = true; + } + + // Chat templates managed by this plugin but no longer present in plugin config + var templatesToRemove = SETTINGS_MANAGER.ConfigurationData.ChatTemplates + .Where(t => t.IsEnterpriseConfiguration && t.EnterpriseConfigurationPluginId == cfgPluginId && !templateIds.Contains(t.Id)) + .ToList(); + foreach (var t in templatesToRemove) + { + LOG.LogWarning($"The configured chat template '{t.Name}' (id={t.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the chat template from the settings."); + SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(t); + wasConfigurationChanged = true; + } + } + #pragma warning restore MWAIS0001 + // // ========================================================== // Check all possible settings: From 18828fbe854e1cadb4f117653ce2b1a46ef02369 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 18 Aug 2025 20:34:04 +0200 Subject: [PATCH 13/14] Add configuration object management for providers and chat templates --- .../Tools/PluginSystem/PluginConfiguration.cs | 103 +++++++++++------- .../PluginSystem/PluginConfigurationObject.cs | 23 ++++ .../PluginConfigurationObjectType.cs | 13 +++ .../PluginSystem/PluginFactory.Loading.cs | 53 ++++----- .../PluginSystem/PluginFactory.Starting.cs | 9 +- 5 files changed, 130 insertions(+), 71 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 70799c56..9e309b10 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -14,7 +14,14 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration)); private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); - + + private readonly List configObjects = []; + + /// + /// The list of configuration objects. Configuration objects are, e.g., providers or chat templates. + /// + public IEnumerable ConfigObjects => this.configObjects; + public async Task InitializeAsync(bool dryRun) { if(!this.TryProcessConfiguration(dryRun, out var issue)) @@ -30,11 +37,13 @@ public async Task InitializeAsync(bool dryRun) /// /// Tries to initialize the UI text content of the plugin. /// - /// When true, the method will not apply any changes, but only check if the configuration can be read. + /// When true, the method will not apply any changes but only check if the configuration can be read. /// The error message, when the UI text content could not be read. /// True, when the UI text content could be read successfully. private bool TryProcessConfiguration(bool dryRun, out string message) { + this.configObjects.Clear(); + // Ensure that the main CONFIG table exists and is a valid Lua table: if (!this.state.Environment["CONFIG"].TryRead(out var mainTable)) { @@ -90,29 +99,38 @@ private bool TryProcessConfiguration(bool dryRun, out string message) // Apply the configured providers to the system settings: // #pragma warning disable MWAIS0001 - if (!dryRun) + foreach (var configuredProvider in configuredProviders) { - 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() { - // The iterating variable is immutable, so we need to create a local copy: - var provider = configuredProvider; + ConfigPluginId = this.Id, + Id = Guid.Parse(provider.Id), + Type = PluginConfigurationObjectType.LLM_PROVIDER, + }); - 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); - } + 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 // @@ -138,26 +156,35 @@ private bool TryProcessConfiguration(bool dryRun, out string message) } // Apply configured chat templates to the system settings: - if (!dryRun) + foreach (var configuredTemplate in configuredTemplates) { - 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 { - // The iterating variable is immutable, so we need to create a local copy: - var template = configuredTemplate; - 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); - } + // Case: The template does not exist, we add it: + template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ }; + SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template); } } } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs new file mode 100644 index 00000000..258e6c3c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -0,0 +1,23 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents metadata for a configuration object from a configuration plugin. These are +/// complex objects such as configured LLM providers, chat templates, etc. +/// +public sealed record PluginConfigurationObject +{ + /// + /// The id of the configuration plugin to which this configuration object belongs. + /// + public required Guid ConfigPluginId { get; init; } = Guid.NewGuid(); + + /// + /// The id of the configuration object, e.g., the id of a chat template. + /// + public required Guid Id { get; init; } = Guid.NewGuid(); + + /// + /// The type of the configuration object. + /// + public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs new file mode 100644 index 00000000..1cb4f604 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs @@ -0,0 +1,13 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum PluginConfigurationObjectType +{ + NONE, + UNKNOWN, + + PROFILE, + DATA_SOURCE, + LLM_PROVIDER, + CHAT_TEMPLATE, + EMBEDDING_PROVIDER, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index d9ac6fde..5972b3a4 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -40,6 +40,8 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken)) return; + var configObjectList = new List(); + try { LOG.LogInformation("Start loading plugins."); @@ -112,7 +114,8 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) } // Start or restart all plugins: - await RestartAllPlugins(cancellationToken); + var configObjects = await RestartAllPlugins(cancellationToken); + configObjectList.AddRange(configObjects); } finally { @@ -149,6 +152,16 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) 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 @@ -172,41 +185,17 @@ public static async Task LoadAll(CancellationToken cancellationToken = default) SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate); wasConfigurationChanged = true; } - } - - // - // Before checking simple settings, validate that still-present configuration plugins haven't removed individual - // providers or chat templates they previously managed. If so, remove those items from our settings as well: - // - #pragma warning disable MWAIS0001 - foreach (var runningPlugin in RUNNING_PLUGINS.OfType()) - { - var (providerIds, templateIds) = runningPlugin.GetManagedObjectIds(); - var cfgPluginId = runningPlugin.Id; - - // Providers managed by this plugin but no longer present in plugin config - var providersToRemove = SETTINGS_MANAGER.ConfigurationData.Providers - .Where(p => p.IsEnterpriseConfiguration && p.EnterpriseConfigurationPluginId == cfgPluginId && !providerIds.Contains(p.Id)) - .ToList(); - foreach (var p in providersToRemove) - { - LOG.LogWarning($"The configured LLM provider '{p.InstanceName}' (id={p.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the provider from the settings."); - SETTINGS_MANAGER.ConfigurationData.Providers.Remove(p); - wasConfigurationChanged = true; - } - - // Chat templates managed by this plugin but no longer present in plugin config - var templatesToRemove = SETTINGS_MANAGER.ConfigurationData.ChatTemplates - .Where(t => t.IsEnterpriseConfiguration && t.EnterpriseConfigurationPluginId == cfgPluginId && !templateIds.Contains(t.Id)) - .ToList(); - foreach (var t in templatesToRemove) + + if(!configObjectList.Any(configObject => + configObject.Type is PluginConfigurationObjectType.CHAT_TEMPLATE && + configObject.ConfigPluginId == templateSourcePluginId && + configObject.Id.ToString() == configuredTemplate.Id)) { - LOG.LogWarning($"The configured chat template '{t.Name}' (id={t.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the chat template from the settings."); - SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(t); + 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; } } - #pragma warning restore MWAIS0001 // // ========================================================== diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs index 0943a48e..5d734b06 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs @@ -11,9 +11,10 @@ public static partial class PluginFactory /// public static IReadOnlyCollection RunningPlugins => RUNNING_PLUGINS; - private static async Task RestartAllPlugins(CancellationToken cancellationToken = default) + private static async Task> RestartAllPlugins(CancellationToken cancellationToken = default) { LOG.LogInformation("Try to start or restart all plugins."); + var configObjects = new List(); RUNNING_PLUGINS.Clear(); // @@ -65,7 +66,12 @@ private static async Task RestartAllPlugins(CancellationToken cancellationToken { if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION) if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) + { + if (plugin is PluginConfiguration configPlugin) + configObjects.AddRange(configPlugin.ConfigObjects); + RUNNING_PLUGINS.Add(plugin); + } } catch (Exception e) { @@ -75,6 +81,7 @@ private static async Task RestartAllPlugins(CancellationToken cancellationToken // Inform all components that the plugins have been reloaded or started: await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); + return configObjects; } private static async Task Start(IAvailablePlugin meta, CancellationToken cancellationToken = default) From 7c1af6c1644c916e7dfc5a0b681d7866ae230d67 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 18 Aug 2025 20:36:48 +0200 Subject: [PATCH 14/14] Updated changelog --- app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md | 1 + 1 file changed, 1 insertion(+) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md index ad4810f9..77ab7d97 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -1,4 +1,5 @@ # v0.9.51, build 226 (2025-08-xx xx:xx UTC) - Added support for predefined chat templates in configuration plugins to help enterprises roll out consistent templates across the organization. - Improved memory usage in several areas of the app. +- Improved plugin management for configuration plugins so that hot reload detects when a provider or chat template has been removed. - Fixed a bug in various assistants where some text fields were not reset when resetting.