From c1826426d90bed17032018292302b128a5caf0c8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 26 Aug 2025 10:59:56 +0200 Subject: [PATCH 1/6] Refactored configuration objects (#538) --- app/MindWork AI Studio/Agents/AgentBase.cs | 7 +- .../Agents/AgentDataSourceSelection.cs | 7 +- .../Agents/AgentRetrievalContextValidation.cs | 7 +- .../Agents/AgentTextContentCleaner.cs | 2 +- app/MindWork AI Studio/Agents/IAgent.cs | 2 +- .../Assistants/AssistantBase.razor.cs | 4 +- .../Assistants/I18N/allTexts.lua | 7 +- .../Components/ChatComponent.razor.cs | 8 +- .../ConfigurationProviderSelection.razor.cs | 3 + .../Components/ProviderSelection.razor.cs | 9 +- .../Components/ReadWebContent.razor.cs | 10 +- .../Settings/SettingsPanelProviders.razor.cs | 3 + app/MindWork AI Studio/Pages/Chat.razor.cs | 2 +- app/MindWork AI Studio/Pages/Writer.razor.cs | 2 +- .../plugin.lua | 7 +- .../plugin.lua | 7 +- .../Settings/ChatTemplate.cs | 107 +++++- .../Settings/ExpressionExtensions.cs | 26 -- app/MindWork AI Studio/Settings/Provider.cs | 115 ++++++- .../Settings/SettingsManager.cs | 16 +- .../Tools/ComponentsExtensions.cs | 51 +-- .../Tools/ExpressionExtensions.cs | 96 ++++++ .../Tools/IncrementResult.cs | 10 + app/MindWork AI Studio/Tools/IncrementType.cs | 14 + .../PluginSystem/ConfigurationBaseObject.cs | 23 ++ .../PluginSystem/IConfigurationObject.cs | 32 ++ .../PluginSystem/NoConfigurationObject.cs | 26 ++ .../Tools/PluginSystem/PluginConfiguration.cs | 321 +----------------- .../PluginSystem/PluginConfigurationObject.cs | 184 ++++++++++ .../PluginSystem/PluginFactory.Loading.cs | 85 +---- .../Tools/Services/DataSourceService.cs | 2 +- 31 files changed, 723 insertions(+), 472 deletions(-) delete mode 100644 app/MindWork AI Studio/Settings/ExpressionExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/ExpressionExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/IncrementResult.cs create mode 100644 app/MindWork AI Studio/Tools/IncrementType.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ConfigurationBaseObject.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/IConfigurationObject.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/NoConfigurationObject.cs diff --git a/app/MindWork AI Studio/Agents/AgentBase.cs b/app/MindWork AI Studio/Agents/AgentBase.cs index 66f13146..2b9239d1 100644 --- a/app/MindWork AI Studio/Agents/AgentBase.cs +++ b/app/MindWork AI Studio/Agents/AgentBase.cs @@ -54,7 +54,7 @@ public abstract class AgentBase(ILogger logger, SettingsManager setti #region Implementation of IAgent - public abstract AIStudio.Settings.Provider? ProviderSettings { get; set; } + public abstract AIStudio.Settings.Provider ProviderSettings { get; set; } public abstract Task ProcessContext(ChatThread chatThread, IDictionary additionalData); @@ -103,10 +103,9 @@ public abstract class AgentBase(ILogger logger, SettingsManager setti protected async Task AddAIResponseAsync(ChatThread thread, IContent lastUserPrompt, DateTimeOffset time) { - if(this.ProviderSettings is null) + if(this.ProviderSettings == Settings.Provider.NONE) return; - var providerSettings = this.ProviderSettings.Value; var aiText = new ContentText { // We have to wait for the remote @@ -127,6 +126,6 @@ public abstract class AgentBase(ILogger logger, SettingsManager setti // Use the selected provider to get the AI response. // By awaiting this line, we wait for the entire // content to be streamed. - await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), providerSettings.Model, lastUserPrompt, thread); + await aiText.CreateFromProviderAsync(this.ProviderSettings.CreateProvider(this.Logger), this.ProviderSettings.Model, lastUserPrompt, thread); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Agents/AgentDataSourceSelection.cs b/app/MindWork AI Studio/Agents/AgentDataSourceSelection.cs index 6fe1f4e2..42d395be 100644 --- a/app/MindWork AI Studio/Agents/AgentDataSourceSelection.cs +++ b/app/MindWork AI Studio/Agents/AgentDataSourceSelection.cs @@ -86,7 +86,7 @@ public sealed class AgentDataSourceSelection (ILogger """; /// - public override Settings.Provider? ProviderSettings { get; set; } + public override Settings.Provider ProviderSettings { get; set; } = Settings.Provider.NONE; /// /// The data source selection agent does not work with context. Use @@ -141,6 +141,11 @@ public sealed class AgentDataSourceSelection (ILogger // We start with the provider currently selected by the user: var agentProvider = this.SettingsManager.GetPreselectedProvider(Tools.Components.AGENT_DATA_SOURCE_SELECTION, provider.Id, true); + if (agentProvider == Settings.Provider.NONE) + { + logger.LogWarning("No provider is selected for the agent. The agent cannot select data sources."); + return []; + } // Assign the provider settings to the agent: logger.LogInformation($"The agent for the data source selection uses the provider '{agentProvider.InstanceName}' ({agentProvider.UsedLLMProvider.ToName()}, confidence={agentProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level.GetName()})."); diff --git a/app/MindWork AI Studio/Agents/AgentRetrievalContextValidation.cs b/app/MindWork AI Studio/Agents/AgentRetrievalContextValidation.cs index 9a4fdff5..7d7b9bb6 100644 --- a/app/MindWork AI Studio/Agents/AgentRetrievalContextValidation.cs +++ b/app/MindWork AI Studio/Agents/AgentRetrievalContextValidation.cs @@ -71,7 +71,7 @@ public sealed class AgentRetrievalContextValidation (ILogger - public override Settings.Provider? ProviderSettings { get; set; } + public override Settings.Provider ProviderSettings { get; set; } = Settings.Provider.NONE; /// /// The retrieval context validation agent does not work with context. Use @@ -133,6 +133,11 @@ public sealed class AgentRetrievalContextValidation (ILogger logger, SettingsM #region Overrides of AgentBase - public override AIStudio.Settings.Provider? ProviderSettings { get; set; } + public override AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE; protected override Type Type => Type.SYSTEM; diff --git a/app/MindWork AI Studio/Agents/IAgent.cs b/app/MindWork AI Studio/Agents/IAgent.cs index e101f2ef..3ee77d8b 100644 --- a/app/MindWork AI Studio/Agents/IAgent.cs +++ b/app/MindWork AI Studio/Agents/IAgent.cs @@ -12,7 +12,7 @@ public interface IAgent /// /// The provider to use for this agent. /// - public AIStudio.Settings.Provider? ProviderSettings { get; set; } + public Settings.Provider ProviderSettings { get; set; } /// /// Processes a chat thread (i.e., context) and returns the updated thread. diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 39bf2659..12ad2757 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -85,7 +85,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher protected virtual IReadOnlyList FooterButtons => []; - protected AIStudio.Settings.Provider providerSettings; + protected AIStudio.Settings.Provider providerSettings = Settings.Provider.NONE; protected MudForm? form; protected bool inputIsValid; protected Profile currentProfile = Profile.NO_PROFILE; @@ -352,7 +352,7 @@ public abstract partial class AssistantBase : AssistantLowerBase wher private async Task InnerResetForm() { this.resultingContentBlock = null; - this.providerSettings = default; + this.providerSettings = Settings.Provider.NONE; await this.JsRuntime.ClearDiv(RESULT_DIV_ID); await this.JsRuntime.ClearDiv(AFTER_RESULT_DIV_ID); diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 338057f4..fe74646a 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -5440,11 +5440,14 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCATEGORYEXTENSIONS::T91464 -- The SETTINGS table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T1148682011"] = "The SETTINGS table does not exist or is not a valid table." +-- At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3262676428"] = "At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist." + -- The CONFIG table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3331620576"] = "The CONFIG table does not exist or is not a valid table." --- The LLM_PROVIDERS table does not exist or is not a valid table. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T806592324"] = "The LLM_PROVIDERS table does not exist or is not a valid table." +-- At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T445358428"] = "At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist." -- The field IETF_TAG does not exist or is not a valid string. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINLANGUAGE::T1796010240"] = "The field IETF_TAG does not exist or is not a valid string." diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 3c4a8d38..6af2a5b7 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -18,9 +18,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable [Parameter] public EventCallback ChatThreadChanged { get; set; } - + [Parameter] - public AIStudio.Settings.Provider Provider { get; set; } + public AIStudio.Settings.Provider Provider { get; set; } = AIStudio.Settings.Provider.NONE; [Parameter] public EventCallback ProviderChanged { get; set; } @@ -634,7 +634,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable default: case AddChatProviderBehavior.ADDED_CHATS_USE_LATEST_PROVIDER: - if(this.Provider == default) + if(this.Provider == AIStudio.Settings.Provider.NONE) { this.Provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT); await this.ProviderChanged.InvokeAsync(this.Provider); @@ -797,7 +797,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable break; case LoadingChatProviderBehavior.ALWAYS_USE_LATEST_CHAT_PROVIDER: - if(this.Provider == default) + if(this.Provider == AIStudio.Settings.Provider.NONE) this.Provider = this.SettingsManager.GetPreselectedProvider(Tools.Components.CHAT); break; } diff --git a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs index 8eced0f2..39ae76be 100644 --- a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs @@ -42,6 +42,9 @@ public partial class ConfigurationProviderSelection : MSGComponentBase foreach (var providerId in this.Data) { var provider = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == providerId.Value); + if (provider is null) + continue; + if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) yield return providerId; } diff --git a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs index b476b19f..ded13ebd 100644 --- a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs @@ -11,9 +11,9 @@ public partial class ProviderSelection : MSGComponentBase { [CascadingParameter] public AssistantBase? AssistantBase { get; set; } - + [Parameter] - public AIStudio.Settings.Provider ProviderSettings { get; set; } + public AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE; [Parameter] public EventCallback ProviderSettingsChanged { get; set; } @@ -32,7 +32,8 @@ public partial class ProviderSelection : MSGComponentBase { var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.AssistantBase?.Component ?? Tools.Components.NONE); foreach (var provider in this.SettingsManager.ConfigurationData.Providers) - if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) - yield return provider; + if (provider.UsedLLMProvider != LLMProviders.NONE) + if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel) + yield return provider; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ReadWebContent.razor.cs b/app/MindWork AI Studio/Components/ReadWebContent.razor.cs index 455c87dd..df007d49 100644 --- a/app/MindWork AI Studio/Components/ReadWebContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadWebContent.razor.cs @@ -20,7 +20,7 @@ public partial class ReadWebContent : MSGComponentBase public EventCallback ContentChanged { get; set; } [Parameter] - public AIStudio.Settings.Provider ProviderSettings { get; set; } + public AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE; [Parameter] public bool AgentIsRunning { get; set; } @@ -34,7 +34,7 @@ public partial class ReadWebContent : MSGComponentBase [Parameter] public bool PreselectContentCleanerAgent { get; set; } - private Process process = Process.INSTANCE; + private readonly Process process = Process.INSTANCE; private ProcessStepValue processStep; private bool showWebContentReader; @@ -43,7 +43,7 @@ public partial class ReadWebContent : MSGComponentBase private bool urlIsValid; private bool isProviderValid; - private AIStudio.Settings.Provider providerSettings; + private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE; #region Overrides of ComponentBase @@ -86,7 +86,7 @@ public partial class ReadWebContent : MSGComponentBase this.StateHasChanged(); markdown = this.HTMLParser.ParseToMarkdown(html); - if (this.useContentCleanerAgent) + if (this.useContentCleanerAgent && this.providerSettings != AIStudio.Settings.Provider.NONE) { this.AgentTextContentCleaner.ProviderSettings = this.providerSettings; var additionalData = new Dictionary @@ -149,7 +149,7 @@ public partial class ReadWebContent : MSGComponentBase private string? ValidateProvider(bool shouldUseAgent) { - if(shouldUseAgent && this.providerSettings == default) + if(shouldUseAgent && this.providerSettings == AIStudio.Settings.Provider.NONE) { this.isProviderValid = false; return T("Please select a provider to use the cleanup agent."); diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index 608dec29..a2e650da 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 @@ public partial class SettingsPanelProviders : SettingsPanelBase [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")] private async Task EditLLMProvider(AIStudio.Settings.Provider provider) { + if(provider == AIStudio.Settings.Provider.NONE) + return; + if (provider.IsEnterpriseConfiguration) return; diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index deeda47f..420828c6 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -21,7 +21,7 @@ public partial class Chat : MSGComponentBase private IDialogService DialogService { get; init; } = null!; private ChatThread? chatThread; - private AIStudio.Settings.Provider providerSettings; + private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE; private bool workspaceOverlayVisible; private string currentWorkspaceName = string.Empty; private Workspaces? workspaces; diff --git a/app/MindWork AI Studio/Pages/Writer.razor.cs b/app/MindWork AI Studio/Pages/Writer.razor.cs index 56fd0c5d..8bd80016 100644 --- a/app/MindWork AI Studio/Pages/Writer.razor.cs +++ b/app/MindWork AI Studio/Pages/Writer.razor.cs @@ -18,7 +18,7 @@ public partial class Writer : MSGComponentBase private readonly Timer typeTimer = new(TimeSpan.FromMilliseconds(1_500)); private MudTextField textField = null!; - private AIStudio.Settings.Provider providerSettings; + private AIStudio.Settings.Provider providerSettings = AIStudio.Settings.Provider.NONE; private ChatThread? chatThread; private bool isStreaming; private string userInput = string.Empty; 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 4777ab01..ec8fe2c2 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 @@ -5442,11 +5442,14 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCATEGORYEXTENSIONS::T91464 -- The SETTINGS table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T1148682011"] = "Die Tabelle SETTINGS existiert nicht oder ist keine gültige Tabelle." +-- At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3262676428"] = "Mindestens ein konfigurierter LLM-Anbieter ist ungültig oder konnte nicht verarbeitet werden, oder die Tabelle LLM_PROVIDERS existiert nicht." + -- The CONFIG table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3331620576"] = "Die Tabelle CONFIG existiert nicht oder ist keine gültige Tabelle." --- The LLM_PROVIDERS table does not exist or is not a valid table. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T806592324"] = "Die Tabelle LLM_PROVIDERS existiert nicht oder ist keine gültige Tabelle." +-- At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T445358428"] = "Mindestens eine konfigurierte Chat-Vorlage ist ungültig oder konnte nicht verarbeitet werden, oder die Tabelle CHAT_TEMPLATES existiert nicht." -- The field IETF_TAG does not exist or is not a valid string. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINLANGUAGE::T1796010240"] = "Das Feld IETF_TAG existiert nicht oder ist keine gültige Zeichenkette." 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 5d546552..fa754177 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 @@ -5442,11 +5442,14 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCATEGORYEXTENSIONS::T91464 -- The SETTINGS table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T1148682011"] = "The SETTINGS table does not exist or is not a valid table." +-- At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3262676428"] = "At least one configured LLM provider is not valid or could not be parsed, or the LLM_PROVIDERS table does not exist." + -- The CONFIG table does not exist or is not a valid table. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T3331620576"] = "The CONFIG table does not exist or is not a valid table." --- The LLM_PROVIDERS table does not exist or is not a valid table. -UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T806592324"] = "The LLM_PROVIDERS table does not exist or is not a valid table." +-- At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINCONFIGURATION::T445358428"] = "At least one configured chat template is not valid or could not be parsed, or the CHAT_TEMPLATES table does not exist." -- The field IETF_TAG does not exist or is not a valid string. UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINLANGUAGE::T1796010240"] = "The field IETF_TAG does not exist or is not a valid string." diff --git a/app/MindWork AI Studio/Settings/ChatTemplate.cs b/app/MindWork AI Studio/Settings/ChatTemplate.cs index 6842ae00..38f3623d 100644 --- a/app/MindWork AI Studio/Settings/ChatTemplate.cs +++ b/app/MindWork AI Studio/Settings/ChatTemplate.cs @@ -1,9 +1,20 @@ using AIStudio.Chat; using AIStudio.Tools.PluginSystem; +using Lua; + namespace AIStudio.Settings; -public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt, string PredefinedUserPrompt, List ExampleConversation, bool AllowProfileUsage, bool IsEnterpriseConfiguration = false, Guid EnterpriseConfigurationPluginId = default) +public record ChatTemplate( + uint Num, + string Id, + string Name, + string SystemPrompt, + string PredefinedUserPrompt, + List ExampleConversation, + bool AllowProfileUsage, + bool IsEnterpriseConfiguration = false, + Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject { public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false) { @@ -11,6 +22,8 @@ public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt 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"), @@ -41,4 +54,96 @@ public record ChatTemplate(uint Num, string Id, string Name, string SystemPrompt 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/ExpressionExtensions.cs b/app/MindWork AI Studio/Settings/ExpressionExtensions.cs deleted file mode 100644 index 5d567c03..00000000 --- a/app/MindWork AI Studio/Settings/ExpressionExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Linq.Expressions; - -namespace AIStudio.Settings; - -public static class ExpressionExtensions -{ - private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(typeof(ExpressionExtensions)); - - public static MemberExpression GetMemberExpression(this Expression> expression) - { - switch (expression.Body) - { - // Case for value types, which are wrapped in UnaryExpression: - case UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression: - return (MemberExpression)unaryExpression.Operand; - - // Case for reference types, which are directly MemberExpressions: - case MemberExpression memberExpression: - return memberExpression; - - default: - LOGGER.LogError($"Expression '{expression}' is not a valid property expression."); - throw new ArgumentException($"Expression '{expression}' is not a valid property expression.", nameof(expression)); - } - } -} \ 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 4cef58df..94a1a747 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -2,6 +2,10 @@ using System.Text.Json.Serialization; using AIStudio.Provider; using AIStudio.Provider.HuggingFace; +using AIStudio.Tools.PluginSystem; + +using Lua; + using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Settings; @@ -16,7 +20,7 @@ namespace AIStudio.Settings; /// Whether the provider is self-hosted. /// The hostname of the provider. Useful for self-hosted providers. /// The LLM model to use for chat. -public readonly record struct Provider( +public sealed record Provider( uint Num, string Id, string InstanceName, @@ -27,8 +31,24 @@ public readonly record struct Provider( Guid EnterpriseConfigurationPluginId = default, string Hostname = "http://localhost:1234", Host Host = Host.NONE, - HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ISecretId + HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ConfigurationBaseObject, ISecretId { + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + + public static readonly Provider NONE = new(); + + public Provider() : this( + 0, + Guid.Empty.ToString(), + string.Empty, + LLMProviders.NONE, + default, + false, + false, + Guid.Empty) + { + } + #region Overrides of ValueType /// @@ -57,4 +77,95 @@ public readonly record struct Provider( public string SecretName => this.InstanceName; #endregion + + #region Implementation of IConfigurationObject + + 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/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 7cad25a2..54e6f1b3 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -215,18 +215,18 @@ public sealed class SettingsManager return this.ConfigurationData.Providers[0]; // Is there a current provider with a sufficiently high confidence level? - Provider currentProvider = default; + var currentProvider = Provider.NONE; if (currentProviderId is not null && !string.IsNullOrWhiteSpace(currentProviderId)) { var currentProviderProbe = this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == currentProviderId); - if (currentProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) + if (currentProviderProbe is not null && currentProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) currentProvider = currentProviderProbe; } // Is there a component-preselected provider with a sufficiently high confidence level? - Provider preselectedProvider = default; + var preselectedProvider = Provider.NONE; var preselectedProviderProbe = component.PreselectedProvider(this); - if(preselectedProviderProbe != default && preselectedProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) + if(preselectedProviderProbe != Provider.NONE && preselectedProviderProbe.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) preselectedProvider = preselectedProviderProbe; // @@ -234,14 +234,14 @@ public sealed class SettingsManager // and the preselected provider is available and has a confidence level // that is high enough. // - if(usePreselectionBeforeCurrentProvider && preselectedProvider != default) + if(usePreselectionBeforeCurrentProvider && preselectedProvider != Provider.NONE) return preselectedProvider; // // Case: The current provider is available and has a confidence level that is // high enough. // - if(currentProvider != default) + if(currentProvider != Provider.NONE) return currentProvider; // @@ -250,11 +250,11 @@ public sealed class SettingsManager // level that is high enough. The preselected provider is available and // has a confidence level that is high enough. // - if(preselectedProvider != default) + if(preselectedProvider != Provider.NONE) return preselectedProvider; // When there is an app-wide preselected provider, and it has a confidence level that is high enough, we return it: - return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && x.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel); + return this.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.ConfigurationData.App.PreselectedProvider && x.UsedLLMProvider.GetConfidence(this).Level >= minimumLevel) ?? Provider.NONE; } public Profile GetPreselectedProfile(Tools.Components component) diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index 18ac4f41..a2126970 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -89,32 +89,37 @@ public static class ComponentsExtensions }; [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")] - public static AIStudio.Settings.Provider PreselectedProvider(this Components component, SettingsManager settingsManager) => component switch + public static AIStudio.Settings.Provider PreselectedProvider(this Components component, SettingsManager settingsManager) { - Components.GRAMMAR_SPELLING_ASSISTANT => settingsManager.ConfigurationData.GrammarSpelling.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider) : default, - Components.ICON_FINDER_ASSISTANT => settingsManager.ConfigurationData.IconFinder.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.IconFinder.PreselectedProvider) : default, - Components.REWRITE_ASSISTANT => settingsManager.ConfigurationData.RewriteImprove.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.RewriteImprove.PreselectedProvider) : default, - Components.TRANSLATION_ASSISTANT => settingsManager.ConfigurationData.Translation.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Translation.PreselectedProvider) : default, - Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Agenda.PreselectedProvider) : default, - Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Coding.PreselectedProvider) : default, - Components.TEXT_SUMMARIZER_ASSISTANT => settingsManager.ConfigurationData.TextSummarizer.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.TextSummarizer.PreselectedProvider) : default, - Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.EMail.PreselectedProvider) : default, - Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.LegalCheck.PreselectedProvider) : default, - Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Synonyms.PreselectedProvider) : default, - Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProvider) : default, - Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.JobPostings.PreselectedProvider) : default, - Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider) : default, - Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.ERI.PreselectedProvider) : default, - Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.I18N.PreselectedProvider) : default, + var preselectedProvider = component switch + { + Components.GRAMMAR_SPELLING_ASSISTANT => settingsManager.ConfigurationData.GrammarSpelling.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider) : null, + Components.ICON_FINDER_ASSISTANT => settingsManager.ConfigurationData.IconFinder.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.IconFinder.PreselectedProvider) : null, + Components.REWRITE_ASSISTANT => settingsManager.ConfigurationData.RewriteImprove.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.RewriteImprove.PreselectedProvider) : null, + Components.TRANSLATION_ASSISTANT => settingsManager.ConfigurationData.Translation.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Translation.PreselectedProvider) : null, + Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Agenda.PreselectedProvider) : null, + Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Coding.PreselectedProvider) : null, + Components.TEXT_SUMMARIZER_ASSISTANT => settingsManager.ConfigurationData.TextSummarizer.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.TextSummarizer.PreselectedProvider) : null, + Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.EMail.PreselectedProvider) : null, + Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.LegalCheck.PreselectedProvider) : null, + Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Synonyms.PreselectedProvider) : null, + Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProvider) : null, + Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.JobPostings.PreselectedProvider) : null, + Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider) : null, + Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.ERI.PreselectedProvider) : null, + Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.I18N.PreselectedProvider) : null, - Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : default, + Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : null, + + Components.AGENT_TEXT_CONTENT_CLEANER => settingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider) : null, + Components.AGENT_DATA_SOURCE_SELECTION => settingsManager.ConfigurationData.AgentDataSourceSelection.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.AgentDataSourceSelection.PreselectedAgentProvider) : null, + Components.AGENT_RETRIEVAL_CONTEXT_VALIDATION => settingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectedAgentProvider) : null, + + _ => Settings.Provider.NONE, + }; - Components.AGENT_TEXT_CONTENT_CLEANER => settingsManager.ConfigurationData.TextContentCleaner.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.TextContentCleaner.PreselectedAgentProvider) : default, - Components.AGENT_DATA_SOURCE_SELECTION => settingsManager.ConfigurationData.AgentDataSourceSelection.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.AgentDataSourceSelection.PreselectedAgentProvider) : default, - Components.AGENT_RETRIEVAL_CONTEXT_VALIDATION => settingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectAgentOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.AgentRetrievalContextValidation.PreselectedAgentProvider) : default, - - _ => default, - }; + return preselectedProvider ?? Settings.Provider.NONE; + } public static Profile PreselectedProfile(this Components component, SettingsManager settingsManager) => component switch { diff --git a/app/MindWork AI Studio/Tools/ExpressionExtensions.cs b/app/MindWork AI Studio/Tools/ExpressionExtensions.cs new file mode 100644 index 00000000..bb57fad3 --- /dev/null +++ b/app/MindWork AI Studio/Tools/ExpressionExtensions.cs @@ -0,0 +1,96 @@ +using System.Linq.Expressions; +using System.Numerics; +using System.Reflection; + +namespace AIStudio.Tools; + +public static class ExpressionExtensions +{ + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(typeof(ExpressionExtensions)); + + /// + /// Extracts the member expression from a given lambda expression representing a property. + /// + /// A lambda expression specifying the property for which the member expression is to be extracted. + /// The lambda expression body must represent member access. + /// The type of the object containing the property referenced in the lambda expression. + /// The type of the property being accessed in the lambda expression. + /// The member expression that represents the property access. + /// Thrown if the provided lambda expression does not represent a valid property expression. + public static MemberExpression GetMemberExpression(this Expression> expression) + { + switch (expression.Body) + { + // Case for value types, which are wrapped in UnaryExpression: + case UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression: + return (MemberExpression)unaryExpression.Operand; + + // Case for reference types, which are directly MemberExpressions: + case MemberExpression memberExpression: + return memberExpression; + + default: + LOGGER.LogError($"Expression '{expression}' is not a valid property expression."); + throw new ArgumentException($"Expression '{expression}' is not a valid property expression.", nameof(expression)); + } + } + + /// + /// Attempts to increment the value of an uint property for a specified object using a + /// provided expression. + /// + /// An expression representing the property to be incremented. The property + /// must be of type uint and belong to the provided object. + /// The object that contains the property referenced by the expression. + /// The type of increment operation to perform (e.g., prefix or postfix). + /// The type of the object that contains the property to be incremented. + /// The type of the property to be incremented. + /// An IncrementResult object containing the result of the increment operation. + public static IncrementResult TryIncrement(this Expression> expression, TIn data, IncrementType type) where TOut : IBinaryInteger + { + // Ensure that the expression body is a member expression: + if (expression.Body is not MemberExpression memberExpression) + return new(false, TOut.Zero); + + // Ensure that the member expression is a property: + if (memberExpression.Member is not PropertyInfo propertyInfo) + return new(false, TOut.Zero); + + // Ensure that the member expression has a target object: + if (memberExpression.Expression is null) + return new(false, TOut.Zero); + + // Get the target object for the expression, which is the object containing the property to increment: + var targetObjectExpression = Expression.Lambda(memberExpression.Expression, expression.Parameters); + + // Compile the lambda expression to get the target object + // (which is the object containing the property to increment): + var targetObject = targetObjectExpression.Compile().DynamicInvoke(data); + + // Was the compilation successful? + if (targetObject is null) + return new(false, TOut.Zero); + + // Read the current value of the property: + if (propertyInfo.GetValue(targetObject) is not TOut value) + return new(false, TOut.Zero); + + // Increment the value: + switch (type) + { + case IncrementType.PRE: + var nextValue = value + TOut.CreateChecked(1); + propertyInfo.SetValue(targetObject, nextValue); + return new(true, nextValue); + + case IncrementType.POST: + var currentValue = value; + var incrementedValue = value + TOut.CreateChecked(1); + propertyInfo.SetValue(targetObject, incrementedValue); + return new(true, currentValue); + + default: + return new(false, TOut.Zero); + } + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/IncrementResult.cs b/app/MindWork AI Studio/Tools/IncrementResult.cs new file mode 100644 index 00000000..76efecd4 --- /dev/null +++ b/app/MindWork AI Studio/Tools/IncrementResult.cs @@ -0,0 +1,10 @@ +using System.Numerics; + +namespace AIStudio.Tools; + +/// +/// Represents the result of an increment operation. It encapsulates whether the operation +/// was successful and the increased value. +/// +/// The type of the incremented value, constrained to implement the IBinaryInteger interface. +public sealed record IncrementResult(bool Success, TOut UpdatedValue) where TOut : IBinaryInteger; \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/IncrementType.cs b/app/MindWork AI Studio/Tools/IncrementType.cs new file mode 100644 index 00000000..a675f009 --- /dev/null +++ b/app/MindWork AI Studio/Tools/IncrementType.cs @@ -0,0 +1,14 @@ +namespace AIStudio.Tools; + +public enum IncrementType +{ + /// + /// Increments the value before returning it. So, the incremented value is returned. + /// + PRE, + + /// + /// Increments the value after returning it. So, the original value is returned. + /// + POST, +} \ 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/IConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/IConfigurationObject.cs new file mode 100644 index 00000000..a46da2ef --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/IConfigurationObject.cs @@ -0,0 +1,32 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a configuration object, such as a chat template or a LLM provider. +/// +public interface IConfigurationObject +{ + /// + /// The unique ID of the configuration object. + /// + public string Id { get; } + + /// + /// The continuous number of the configuration object. + /// + public uint Num { get; } + + /// + /// The name of the configuration object. + /// + public string Name { get; } + + /// + /// Is this configuration object an enterprise configuration? + /// + public bool IsEnterpriseConfiguration { get; } + + /// + /// The ID of the enterprise configuration plugin. + /// + public Guid EnterpriseConfigurationPluginId { get; } +} \ 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 258e6c3c..5be55ddb 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -1,3 +1,10 @@ +using System.Linq.Expressions; + +using AIStudio.Settings; +using AIStudio.Settings.DataModel; + +using Lua; + namespace AIStudio.Tools.PluginSystem; /// @@ -6,6 +13,9 @@ namespace AIStudio.Tools.PluginSystem; /// public sealed record PluginConfigurationObject { + private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); + /// /// The id of the configuration plugin to which this configuration object belongs. /// @@ -20,4 +30,178 @@ public sealed record PluginConfigurationObject /// The type of the configuration object. /// 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 + { + 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; + } + + /// + /// Cleans up configuration objects of a specified type that are no longer associated with any available plugin. + /// + /// The type of configuration object to clean up. + /// The type of configuration object to process. + /// A selection expression to retrieve the configuration objects from the main configuration. + /// A list of currently available plugins. + /// A list of all existing configuration objects. + /// Returns true if the configuration was altered during cleanup; otherwise, false. + public static bool CleanLeftOverConfigurationObjects( + PluginConfigurationObjectType configObjectType, + Expression>> configObjectSelection, + IList availablePlugins, + IList configObjectList) where TClass : IConfigurationObject + { + var configuredObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData); + var leftOverObjects = new List(); + 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; + } } \ 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 5972b3a4..2553b537 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -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(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(x => x.App, x => x.AllowUserToAddProvider, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; diff --git a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs index f545aca5..06804474 100644 --- a/app/MindWork AI Studio/Tools/Services/DataSourceService.cs +++ b/app/MindWork AI Studio/Tools/Services/DataSourceService.cs @@ -37,7 +37,7 @@ public sealed class DataSourceService // does not mean anything. We cannot filter the data sources by any means. // We return an empty list. Better safe than sorry. // - if (selectedLLMProvider == default) + if (selectedLLMProvider == Settings.Provider.NONE) { this.logger.LogWarning("The selected LLM provider is not set. We cannot filter the data sources by any means."); return new([], []); From 5d17dcc29b719673af8cee6ca6da1fe55b5ccee8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 26 Aug 2025 20:06:13 +0200 Subject: [PATCH 2/6] Added option for update installation behavior (#539) --- .../Assistants/I18N/allTexts.lua | 15 ++++++++ .../Settings/SettingsPanelApp.razor | 3 +- .../Layout/MainLayout.razor.cs | 7 +++- .../Plugins/configuration/plugin.lua | 8 +++-- .../plugin.lua | 15 ++++++++ .../plugin.lua | 15 ++++++++ .../ConfigurationSelectDataFactory.cs | 18 ++++++---- .../Settings/DataModel/DataApp.cs | 7 +++- .../DataModel/PreviousModels/DataV1V3.cs | 2 +- .../Settings/DataModel/UpdateInstallation.cs | 7 ++++ .../{UpdateBehavior.cs => UpdateInterval.cs} | 2 +- .../Settings/SettingsMigrations.cs | 6 ++-- app/MindWork AI Studio/Tools/Event.cs | 1 + .../Tools/PluginSystem/PluginConfiguration.cs | 5 ++- .../PluginSystem/PluginFactory.Loading.cs | 6 +++- .../Tools/Services/UpdateService.cs | 34 ++++++++++++++----- .../wwwroot/changelog/v0.9.51.md | 3 ++ 17 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 app/MindWork AI Studio/Settings/DataModel/UpdateInstallation.cs rename app/MindWork AI Studio/Settings/DataModel/{UpdateBehavior.cs => UpdateInterval.cs} (79%) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index fe74646a..c75bae13 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -1828,6 +1828,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] -- Language behavior UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" +-- Update installation method +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"] = "Update installation method" + -- Language UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language" @@ -1858,6 +1861,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] -- Choose the color theme that best suits for you. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T654667432"] = "Choose the color theme that best suits for you." +-- Should updates be installed automatically or manually? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T707880477"] = "Should updates be installed automatically or manually?" + -- Energy saving is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = "Energy saving is enabled" @@ -4870,6 +4876,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2128088682 -- Navigation expands on mouse hover UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2195945406"] = "Navigation expands on mouse hover" +-- Install updates manually +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T220653235"] = "Install updates manually" + -- Also show features ready for release; these should be stable UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2301448762"] = "Also show features ready for release; these should be stable" @@ -4909,6 +4918,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3137986690 -- Delete disappearing chats older than 180 days UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3491430707"] = "Delete disappearing chats older than 180 days" +-- Install updates automatically +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3569059463"] = "Install updates automatically" + -- Disable workspaces UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3612390107"] = "Disable workspaces" @@ -5575,6 +5587,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] -- No update found. UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found." +-- Failed to install update automatically. Please try again manually. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T3709709946"] = "Failed to install update automatically. Please try again manually." + -- The hostname is not a valid HTTP(S) URL. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T1013354736"] = "The hostname is not a valid HTTP(S) URL." diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor index fc466e7a..14f187e9 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor @@ -13,7 +13,8 @@ - + + diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index cc115a2c..079b4161 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -92,7 +92,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan [ Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR, Event.SHOW_ERROR, Event.SHOW_WARNING, Event.SHOW_SUCCESS, Event.STARTUP_PLUGIN_SYSTEM, - Event.PLUGINS_RELOADED + Event.PLUGINS_RELOADED, Event.INSTALL_UPDATE, ]); // Set the snackbar for the update service: @@ -143,6 +143,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan { switch (triggeredEvent) { + case Event.INSTALL_UPDATE: + this.performingUpdate = true; + this.StateHasChanged(); + break; + case Event.UPDATE_AVAILABLE: if (data is UpdateResponse updateResponse) { diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 5513e016..7e24e999 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -62,9 +62,13 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = { CONFIG["SETTINGS"] = {} --- Configure the update behavior: +-- Configure the update check interval: -- Allowed values are: NO_CHECK, ONCE_STARTUP, HOURLY, DAILY, WEEKLY --- CONFIG["SETTINGS"]["DataApp.UpdateBehavior"] = "NO_CHECK" +-- CONFIG["SETTINGS"]["DataApp.UpdateInterval"] = "NO_CHECK" + +-- Configure how updates are installed: +-- Allowed values are: MANUAL, AUTOMATIC +-- CONFIG["SETTINGS"]["DataApp.UpdateInstallation"] = "MANUAL" -- Configure the user permission to add providers: -- Allowed values are: true, false 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 ec8fe2c2..c1777807 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 @@ -1830,6 +1830,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] -- Language behavior UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Sprachverhalten" +-- Update installation method +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"] = "Installationsmethode für Updates" + -- Language UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Sprache" @@ -1860,6 +1863,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] -- Choose the color theme that best suits for you. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T654667432"] = "Wählen Sie das Farbschema, das am besten zu Ihnen passt." +-- Should updates be installed automatically or manually? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T707880477"] = "Sollen Updates automatisch oder manuell installiert werden?" + -- Energy saving is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = "Energiesparmodus ist aktiviert" @@ -4872,6 +4878,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2128088682 -- Navigation expands on mouse hover UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2195945406"] = "Navigationsleiste erweitert sich, wenn sich die Maus darüber befindet" +-- Install updates manually +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T220653235"] = "Updates manuell installieren" + -- Also show features ready for release; these should be stable UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2301448762"] = "Auch Funktionen anzeigen, die bereit für die Veröffentlichung sind; diese sollten stabil sein." @@ -4911,6 +4920,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3137986690 -- Disappearing chats: delete chats older than 180 days UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3491430707"] = "Selbstlöschende Chats: lösche Chats die älter als 180 Tage sind" +-- Install updates automatically +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3569059463"] = "Updates automatisch installieren" + -- Disable workspaces UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3612390107"] = "Arbeitsbereiche deaktivieren" @@ -5577,6 +5589,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] -- No update found. UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "Kein Update gefunden." +-- Failed to install update automatically. Please try again manually. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T3709709946"] = "Fehler bei der automatischen Installation des Updates. Bitte versuchen Sie es manuell erneut." + -- The hostname is not a valid HTTP(S) URL. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T1013354736"] = "Der Hostname ist keine gültige HTTP(S)-URL." 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 fa754177..d7104563 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 @@ -1830,6 +1830,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] -- Language behavior UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" +-- Update installation method +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"] = "Update installation method" + -- Language UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language" @@ -1860,6 +1863,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] -- Choose the color theme that best suits for you. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T654667432"] = "Choose the color theme that best suits for you." +-- Should updates be installed automatically or manually? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T707880477"] = "Should updates be installed automatically or manually?" + -- Energy saving is enabled UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = "Energy saving is enabled" @@ -4872,6 +4878,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2128088682 -- Navigation expands on mouse hover UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2195945406"] = "Navigation expands on mouse hover" +-- Install updates manually +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T220653235"] = "Install updates manually" + -- Also show features ready for release; these should be stable UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T2301448762"] = "Also show features ready for release; these should be stable" @@ -4911,6 +4920,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3137986690 -- Disappearing chats: delete chats older than 180 days UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3491430707"] = "Disappearing chats: delete chats older than 180 days" +-- Install updates automatically +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3569059463"] = "Install updates automatically" + -- Disable workspaces UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T3612390107"] = "Disable workspaces" @@ -5577,6 +5589,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] -- No update found. UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found." +-- Failed to install update automatically. Please try again manually. +UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T3709709946"] = "Failed to install update automatically. Please try again manually." + -- The hostname is not a valid HTTP(S) URL. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T1013354736"] = "The hostname is not a valid HTTP(S) URL." diff --git a/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs b/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs index d14868fc..5004dc43 100644 --- a/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs +++ b/app/MindWork AI Studio/Settings/ConfigurationSelectDataFactory.cs @@ -63,13 +63,19 @@ public static class ConfigurationSelectDataFactory yield return new(TB("Enter is sending the input"), SendBehavior.ENTER_IS_SENDING); } - public static IEnumerable> GetUpdateBehaviorData() + public static IEnumerable> GetUpdateIntervalData() { - yield return new(TB("No automatic update checks"), UpdateBehavior.NO_CHECK); - yield return new(TB("Once at startup"), UpdateBehavior.ONCE_STARTUP); - yield return new(TB("Check every hour"), UpdateBehavior.HOURLY); - yield return new(TB("Check every day"), UpdateBehavior.DAILY); - yield return new (TB("Check every week"), UpdateBehavior.WEEKLY); + yield return new(TB("No automatic update checks"), UpdateInterval.NO_CHECK); + yield return new(TB("Once at startup"), UpdateInterval.ONCE_STARTUP); + yield return new(TB("Check every hour"), UpdateInterval.HOURLY); + yield return new(TB("Check every day"), UpdateInterval.DAILY); + yield return new (TB("Check every week"), UpdateInterval.WEEKLY); + } + + public static IEnumerable> GetUpdateBehaviourData() + { + yield return new(TB("Install updates manually"), UpdateInstallation.MANUAL); + yield return new(TB("Install updates automatically"), UpdateInstallation.AUTOMATIC); } public static IEnumerable> GetWorkspaceStorageBehaviorData() diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index 477afa30..67be8b9b 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -40,7 +40,12 @@ public sealed class DataApp(Expression>? configSelection = n /// /// If and when we should look for updates. /// - public UpdateBehavior UpdateBehavior { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UpdateBehavior, UpdateBehavior.HOURLY); + public UpdateInterval UpdateInterval { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UpdateInterval, UpdateInterval.HOURLY); + + /// + /// How updates should be installed. + /// + public UpdateInstallation UpdateInstallation { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UpdateInstallation, UpdateInstallation.MANUAL); /// /// The navigation behavior. diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV1V3.cs b/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV1V3.cs index 0b1c7883..88a8a3d2 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV1V3.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviousModels/DataV1V3.cs @@ -41,7 +41,7 @@ public sealed class DataV1V3 /// /// If and when we should look for updates. /// - public UpdateBehavior UpdateBehavior { get; set; } = UpdateBehavior.ONCE_STARTUP; + public UpdateInterval UpdateInterval { get; set; } = UpdateInterval.ONCE_STARTUP; /// /// The navigation behavior. diff --git a/app/MindWork AI Studio/Settings/DataModel/UpdateInstallation.cs b/app/MindWork AI Studio/Settings/DataModel/UpdateInstallation.cs new file mode 100644 index 00000000..50d2a296 --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/UpdateInstallation.cs @@ -0,0 +1,7 @@ +namespace AIStudio.Settings.DataModel; + +public enum UpdateInstallation +{ + MANUAL, + AUTOMATIC, +} diff --git a/app/MindWork AI Studio/Settings/DataModel/UpdateBehavior.cs b/app/MindWork AI Studio/Settings/DataModel/UpdateInterval.cs similarity index 79% rename from app/MindWork AI Studio/Settings/DataModel/UpdateBehavior.cs rename to app/MindWork AI Studio/Settings/DataModel/UpdateInterval.cs index 0b82604f..a7a3ec8c 100644 --- a/app/MindWork AI Studio/Settings/DataModel/UpdateBehavior.cs +++ b/app/MindWork AI Studio/Settings/DataModel/UpdateInterval.cs @@ -1,6 +1,6 @@ namespace AIStudio.Settings.DataModel; -public enum UpdateBehavior +public enum UpdateInterval { NO_CHECK, ONCE_STARTUP, diff --git a/app/MindWork AI Studio/Settings/SettingsMigrations.cs b/app/MindWork AI Studio/Settings/SettingsMigrations.cs index 7c5a4293..e5041817 100644 --- a/app/MindWork AI Studio/Settings/SettingsMigrations.cs +++ b/app/MindWork AI Studio/Settings/SettingsMigrations.cs @@ -90,7 +90,7 @@ public static class SettingsMigrations IsSavingEnergy = previousData.IsSavingEnergy, NextProviderNum = previousData.NextProviderNum, ShortcutSendBehavior = previousData.ShortcutSendBehavior, - UpdateBehavior = previousData.UpdateBehavior, + UpdateInterval = previousData.UpdateInterval, }; } @@ -117,7 +117,7 @@ public static class SettingsMigrations IsSavingEnergy = previousData.IsSavingEnergy, NextProviderNum = previousData.NextProviderNum, ShortcutSendBehavior = previousData.ShortcutSendBehavior, - UpdateBehavior = previousData.UpdateBehavior, + UpdateInterval = previousData.UpdateInterval, WorkspaceStorageBehavior = previousData.WorkspaceStorageBehavior, WorkspaceStorageTemporaryMaintenancePolicy = previousData.WorkspaceStorageTemporaryMaintenancePolicy, }; @@ -141,7 +141,7 @@ public static class SettingsMigrations { EnableSpellchecking = previousConfig.EnableSpellchecking, IsSavingEnergy = previousConfig.IsSavingEnergy, - UpdateBehavior = previousConfig.UpdateBehavior, + UpdateInterval = previousConfig.UpdateInterval, NavigationBehavior = previousConfig.NavigationBehavior, }, diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index fc8ca8e3..fcf32604 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -18,6 +18,7 @@ public enum Event // Update events: USER_SEARCH_FOR_UPDATE, UPDATE_AVAILABLE, + INSTALL_UPDATE, // Chat events: HAS_CHAT_UNSAVED_CHANGES, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 5e5efd3e..28daf3d1 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -53,7 +53,10 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT } // Config: check for updates, and if so, how often? - ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UpdateBehavior, this.Id, settingsTable, dryRun); + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UpdateInterval, this.Id, settingsTable, dryRun); + + // Config: how should updates be installed? + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.UpdateInstallation, this.Id, settingsTable, dryRun); // Config: allow the user to add providers? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.AllowUserToAddProvider, this.Id, settingsTable, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 2553b537..92f77344 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -139,7 +139,11 @@ public static partial class PluginFactory wasConfigurationChanged = true; // Check for update behavior: - if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateBehavior, AVAILABLE_PLUGINS)) + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateInterval, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + + // Check for update installation behavior: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.UpdateInstallation, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; // Check for users allowed to added providers: diff --git a/app/MindWork AI Studio/Tools/Services/UpdateService.cs b/app/MindWork AI Studio/Tools/Services/UpdateService.cs index 7cda6443..75335521 100644 --- a/app/MindWork AI Studio/Tools/Services/UpdateService.cs +++ b/app/MindWork AI Studio/Tools/Services/UpdateService.cs @@ -42,14 +42,14 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver // // Set the update interval based on the user's settings. // - this.updateInterval = this.settingsManager.ConfigurationData.App.UpdateBehavior switch + this.updateInterval = this.settingsManager.ConfigurationData.App.UpdateInterval switch { - UpdateBehavior.NO_CHECK => Timeout.InfiniteTimeSpan, - UpdateBehavior.ONCE_STARTUP => Timeout.InfiniteTimeSpan, + UpdateInterval.NO_CHECK => Timeout.InfiniteTimeSpan, + UpdateInterval.ONCE_STARTUP => Timeout.InfiniteTimeSpan, - UpdateBehavior.HOURLY => TimeSpan.FromHours(1), - UpdateBehavior.DAILY => TimeSpan.FromDays(1), - UpdateBehavior.WEEKLY => TimeSpan.FromDays(7), + UpdateInterval.HOURLY => TimeSpan.FromHours(1), + UpdateInterval.DAILY => TimeSpan.FromDays(1), + UpdateInterval.WEEKLY => TimeSpan.FromDays(7), _ => TimeSpan.FromHours(1) }; @@ -58,7 +58,7 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver // When the user doesn't want to check for updates, we can // return early. // - if(this.settingsManager.ConfigurationData.App.UpdateBehavior is UpdateBehavior.NO_CHECK) + if(this.settingsManager.ConfigurationData.App.UpdateInterval is UpdateInterval.NO_CHECK) return; // @@ -115,7 +115,25 @@ public sealed class UpdateService : BackgroundService, IMessageBusReceiver var response = await this.rust.CheckForUpdate(); if (response.UpdateIsAvailable) { - await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response); + if (this.settingsManager.ConfigurationData.App.UpdateInstallation is UpdateInstallation.AUTOMATIC) + { + try + { + await this.messageBus.SendMessage(null, Event.INSTALL_UPDATE); + await this.rust.InstallUpdate(); + } + catch (Exception) + { + SNACKBAR!.Add(TB("Failed to install update automatically. Please try again manually."), Severity.Error, config => + { + config.Icon = Icons.Material.Filled.Error; + config.IconSize = Size.Large; + config.IconColor = Color.Error; + }); + } + } + else + await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response); } else { 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 77ab7d97..2b70b396 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -1,5 +1,8 @@ # 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. +- Added the ability to choose between automatic and manual update installation to the app settings (default is manual). +- Added the ability to control the update installation behavior by configuration plugins. - 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. +- Changed the configuration plugin setting name for how often to check for updates from `UpdateBehavior` to `UpdateInterval`. - Fixed a bug in various assistants where some text fields were not reset when resetting. From e931e28ab1714ede87860d9f07325e0c47d712e6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 28 Aug 2025 16:44:32 +0200 Subject: [PATCH 3/6] Improved workspace handling (#540) --- .../Assistants/I18N/allTexts.lua | 9 +++ .../Components/Workspaces.razor.cs | 75 +++++++++++++++++-- .../plugin.lua | 9 +++ .../plugin.lua | 10 +++ .../Tools/WorkspaceBehaviour.cs | 57 ++++++++++++-- .../wwwroot/changelog/v0.9.51.md | 1 + 6 files changed, 148 insertions(+), 13 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index c75bae13..816357ba 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2119,6 +2119,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure -- Move chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat" +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1307384014"] = "Unnamed workspace" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete" @@ -2164,6 +2167,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" +-- Unnamed chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unnamed chat" + -- Rename UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename" @@ -5692,5 +5698,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] = -- Please select a model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Please select a model." +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace" + -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat" diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index cbce4fdd..beb10a24 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -112,9 +112,30 @@ public partial class Workspaces : MSGComponentBase // Enumerate the chat directories: foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories)) { - // Read the `name` file: + // Read or create the `name` file (self-heal): var chatNamePath = Path.Join(tempChatDirPath, "name"); - var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); + string chatName; + try + { + if (!File.Exists(chatNamePath)) + { + chatName = T("Unnamed chat"); + await File.WriteAllTextAsync(chatNamePath, chatName, Encoding.UTF8); + } + else + { + chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); + if (string.IsNullOrWhiteSpace(chatName)) + { + chatName = T("Unnamed chat"); + await File.WriteAllTextAsync(chatNamePath, chatName, Encoding.UTF8); + } + } + } + catch + { + chatName = T("Unnamed chat"); + } // Read the last change time of the chat: var chatThreadPath = Path.Join(tempChatDirPath, "thread.json"); @@ -158,9 +179,30 @@ public partial class Workspaces : MSGComponentBase // Enumerate the workspace directories: foreach (var workspaceDirPath in Directory.EnumerateDirectories(workspaceDirectories)) { - // Read the `name` file: + // Read or create the `name` file (self-heal): var workspaceNamePath = Path.Join(workspaceDirPath, "name"); - var workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + string workspaceName; + try + { + if (!File.Exists(workspaceNamePath)) + { + workspaceName = T("Unnamed workspace"); + await File.WriteAllTextAsync(workspaceNamePath, workspaceName, Encoding.UTF8); + } + else + { + workspaceName = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + if (string.IsNullOrWhiteSpace(workspaceName)) + { + workspaceName = T("Unnamed workspace"); + await File.WriteAllTextAsync(workspaceNamePath, workspaceName, Encoding.UTF8); + } + } + } + catch + { + workspaceName = T("Unnamed workspace"); + } workspaces.Add(new TreeItemData { @@ -194,9 +236,30 @@ public partial class Workspaces : MSGComponentBase // Enumerate the workspace directory: foreach (var chatPath in Directory.EnumerateDirectories(workspacePath)) { - // Read the `name` file: + // Read or create the `name` file (self-heal): var chatNamePath = Path.Join(chatPath, "name"); - var chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); + string chatName; + try + { + if (!File.Exists(chatNamePath)) + { + chatName = T("Unnamed chat"); + await File.WriteAllTextAsync(chatNamePath, chatName, Encoding.UTF8); + } + else + { + chatName = await File.ReadAllTextAsync(chatNamePath, Encoding.UTF8); + if (string.IsNullOrWhiteSpace(chatName)) + { + chatName = T("Unnamed chat"); + await File.WriteAllTextAsync(chatNamePath, chatName, Encoding.UTF8); + } + } + } + catch + { + chatName = T("Unnamed chat"); + } // Read the last change time of the chat: var chatThreadPath = Path.Join(chatPath, "thread.json"); 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 c1777807..948e6972 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 @@ -2121,6 +2121,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Möchten Sie -- Move chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Chat verschieben" +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1307384014"] = "Unbenannter Arbeitsbereich" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Löschen" @@ -2166,6 +2169,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Chat in den -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Bitte geben Sie einen neuen Namen für ihren Arbeitsbereich „{0}“ ein oder bearbeiten Sie ihn:" +-- Unnamed chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unbenannter Chat" + -- Rename UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Umbenennen" @@ -5694,5 +5700,8 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] = -- Please select a model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Bitte wählen Sie ein Modell aus." +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unbenannter Arbeitsbereich" + -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Chat löschen" 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 d7104563..017b5e0c 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 @@ -2121,6 +2121,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure -- Move chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat" +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1307384014"] = "Unnamed workspace" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete" @@ -2166,6 +2169,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" +-- Unnamed chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unnamed chat" + -- Rename UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename" @@ -5694,5 +5700,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T497939286"] = -- Please select a model. UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] = "Please select a model." +-- Unnamed workspace +UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace" + -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat" + diff --git a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs index 00a6d68f..66d665ca 100644 --- a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs +++ b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs @@ -83,7 +83,33 @@ public static class WorkspaceBehaviour var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString()); var workspaceNamePath = Path.Join(workspacePath, "name"); - return await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + + try + { + // If the name file does not exist or is empty, self-heal with a default name. + if (!File.Exists(workspaceNamePath)) + { + var defaultName = TB("Unnamed workspace"); + Directory.CreateDirectory(workspacePath); + await File.WriteAllTextAsync(workspaceNamePath, defaultName, Encoding.UTF8); + return defaultName; + } + + var name = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + if (string.IsNullOrWhiteSpace(name)) + { + var defaultName = TB("Unnamed workspace"); + await File.WriteAllTextAsync(workspaceNamePath, defaultName, Encoding.UTF8); + return defaultName; + } + + return name; + } + catch + { + // On any error, return a localized default without throwing. + return TB("Unnamed workspace"); + } } public static async Task DeleteChat(IDialogService dialogService, Guid workspaceId, Guid chatId, bool askForConfirmation = true) @@ -124,13 +150,30 @@ public static class WorkspaceBehaviour private static async Task EnsureWorkspace(Guid workspaceId, string workspaceName) { var workspacePath = Path.Join(SettingsManager.DataDirectory, "workspaces", workspaceId.ToString()); - - if(Path.Exists(workspacePath)) - return; - - Directory.CreateDirectory(workspacePath); var workspaceNamePath = Path.Join(workspacePath, "name"); - await File.WriteAllTextAsync(workspaceNamePath, workspaceName, Encoding.UTF8); + + if(!Path.Exists(workspacePath)) + Directory.CreateDirectory(workspacePath); + + try + { + // When the name file is missing or empty, write it (self-heal). + // Otherwise, keep the existing name: + if (!File.Exists(workspaceNamePath)) + { + await File.WriteAllTextAsync(workspaceNamePath, workspaceName, Encoding.UTF8); + } + else + { + var existing = await File.ReadAllTextAsync(workspaceNamePath, Encoding.UTF8); + if (string.IsNullOrWhiteSpace(existing)) + await File.WriteAllTextAsync(workspaceNamePath, workspaceName, Encoding.UTF8); + } + } + catch + { + // Ignore IO issues to avoid interrupting background initialization. + } } public static async Task EnsureBiasWorkspace() => await EnsureWorkspace(KnownWorkspaces.BIAS_WORKSPACE_ID, "Bias of the Day"); 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 2b70b396..63b7c673 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -6,3 +6,4 @@ - Improved plugin management for configuration plugins so that hot reload detects when a provider or chat template has been removed. - Changed the configuration plugin setting name for how often to check for updates from `UpdateBehavior` to `UpdateInterval`. - Fixed a bug in various assistants where some text fields were not reset when resetting. +- Fixed a rare chat-related bug that could occur when a workspace was not created correctly. Thank you, Naomi, for reporting this issue. \ No newline at end of file From 99dc2606aaed66b3ee7c1c105d5084b6be205e14 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 28 Aug 2025 18:16:30 +0200 Subject: [PATCH 4/6] Improved input validation for the single input dialog (#541) --- .../Assistants/I18N/allTexts.lua | 9 +++++ .../Components/Workspaces.razor.cs | 36 +++++++++++-------- .../Dialogs/SingleInputDialog.razor | 4 ++- .../Dialogs/SingleInputDialog.razor.cs | 27 ++++++++++++-- .../plugin.lua | 9 +++++ .../plugin.lua | 9 +++++ .../wwwroot/changelog/v0.9.51.md | 1 + 7 files changed, 77 insertions(+), 18 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 816357ba..212ee492 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2155,6 +2155,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" +-- Please enter a chat name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" @@ -2167,6 +2170,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" +-- Please enter a workspace name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Please enter a workspace name." + -- Unnamed chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unnamed chat" @@ -4135,6 +4141,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004 -- Chat name UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat name" +-- Please enter a value. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value." + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index beb10a24..1f86dd3c 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -396,12 +396,14 @@ public partial class Workspaces : MSGComponentBase if (chat is null) return; - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Please enter a new or edit the name for your chat '{0}':"), chat.Name) }, - { "UserInput", chat.Name }, - { "ConfirmText", T("Rename") }, - { "ConfirmColor", Color.Info }, + { x => x.Message, string.Format(T("Please enter a new or edit the name for your chat '{0}':"), chat.Name) }, + { x => x.UserInput, chat.Name }, + { x => x.ConfirmText, T("Rename") }, + { x => x.ConfirmColor, Color.Info }, + { x => x.AllowEmptyInput, false }, + { x => x.EmptyInputErrorMessage, T("Please enter a chat name.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Rename Chat"), dialogParameters, DialogOptions.FULLSCREEN); @@ -428,12 +430,14 @@ public partial class Workspaces : MSGComponentBase var workspaceId = Guid.Parse(Path.GetFileName(workspacePath)); var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Please enter a new or edit the name for your workspace '{0}':"), workspaceName) }, - { "UserInput", workspaceName }, - { "ConfirmText", T("Rename") }, - { "ConfirmColor", Color.Info }, + { x => x.Message, string.Format(T("Please enter a new or edit the name for your workspace '{0}':"), workspaceName) }, + { x => x.UserInput, workspaceName }, + { x => x.ConfirmText, T("Rename") }, + { x => x.ConfirmColor, Color.Info }, + { x => x.AllowEmptyInput, false }, + { x => x.EmptyInputErrorMessage, T("Please enter a workspace name.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Rename Workspace"), dialogParameters, DialogOptions.FULLSCREEN); @@ -449,12 +453,14 @@ public partial class Workspaces : MSGComponentBase private async Task AddWorkspace() { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Please name your workspace:") }, - { "UserInput", string.Empty }, - { "ConfirmText", T("Add workspace") }, - { "ConfirmColor", Color.Info }, + { x => x.Message, T("Please name your workspace:") }, + { x => x.UserInput, string.Empty }, + { x => x.ConfirmText, T("Add workspace") }, + { x => x.ConfirmColor, Color.Info }, + { x => x.AllowEmptyInput, false }, + { x => x.EmptyInputErrorMessage, T("Please enter a workspace name.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Add Workspace"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor index 1583fc04..5e4bb80c 100644 --- a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor +++ b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor @@ -4,7 +4,9 @@ @this.Message - + + + diff --git a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs index c4686571..08d3d94d 100644 --- a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs @@ -20,9 +20,17 @@ public partial class SingleInputDialog : MSGComponentBase [Parameter] public Color ConfirmColor { get; set; } = Color.Error; - + + [Parameter] + public bool AllowEmptyInput { get; set; } + + [Parameter] + public string EmptyInputErrorMessage { get; set; } = string.Empty; + private static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); + private MudForm form = null!; + #region Overrides of ComponentBase protected override async Task OnInitializedAsync() @@ -34,7 +42,22 @@ public partial class SingleInputDialog : MSGComponentBase #endregion + private string? ValidateUserInput(string? value) + { + if (!this.AllowEmptyInput && string.IsNullOrWhiteSpace(value)) + return string.IsNullOrWhiteSpace(this.EmptyInputErrorMessage) ? T("Please enter a value.") : this.EmptyInputErrorMessage; + + return null; + } + private void Cancel() => this.MudDialog.Cancel(); - private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.UserInput)); + private async Task Confirm() + { + await this.form.Validate(); + if(!this.form.IsValid) + return; + + this.MudDialog.Close(DialogResult.Ok(this.UserInput)); + } } \ No newline at end of file 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 948e6972..bab0ac14 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 @@ -2157,6 +2157,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Möchten Sie -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Chat löschen" +-- Please enter a chat name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Bitte geben Sie einen Namen für diesen Chat ein." + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "In einen Arbeitsbereich verschieben" @@ -2169,6 +2172,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Chat in den -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Bitte geben Sie einen neuen Namen für ihren Arbeitsbereich „{0}“ ein oder bearbeiten Sie ihn:" +-- Please enter a workspace name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Bitte geben Sie einen Namen für diesen Arbeitsbereich ein." + -- Unnamed chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unbenannter Chat" @@ -4137,6 +4143,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004 -- Chat name UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat-Name" +-- Please enter a value. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Bitte geben Sie einen Wert ein." + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Abbrechen" 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 017b5e0c..75575e12 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 @@ -2157,6 +2157,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" +-- Please enter a chat name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" @@ -2169,6 +2172,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to -- Please enter a new or edit the name for your workspace '{0}': UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" +-- Please enter a workspace name. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3288132732"] = "Please enter a workspace name." + -- Unnamed chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3310482275"] = "Unnamed chat" @@ -4137,6 +4143,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004 -- Chat name UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat name" +-- Please enter a value. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value." + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" 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 63b7c673..0155d75c 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -4,6 +4,7 @@ - Added the ability to control the update installation behavior by configuration plugins. - 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. +- Improved the dialog for naming chats and workspaces to ensure valid inputs are entered. - Changed the configuration plugin setting name for how often to check for updates from `UpdateBehavior` to `UpdateInterval`. - Fixed a bug in various assistants where some text fields were not reset when resetting. - Fixed a rare chat-related bug that could occur when a workspace was not created correctly. Thank you, Naomi, for reporting this issue. \ No newline at end of file From e03289b68016a377b0f464d6f50db24f2ef1fec9 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 28 Aug 2025 18:31:50 +0200 Subject: [PATCH 5/6] Fixed input field header for single input dialog (#542) --- app/MindWork AI Studio/Assistants/I18N/allTexts.lua | 12 +++++++++--- .../Components/Workspaces.razor.cs | 3 +++ .../Dialogs/SingleInputDialog.razor | 2 +- .../Dialogs/SingleInputDialog.razor.cs | 5 +++++ .../plugin.lua | 12 +++++++++--- .../plugin.lua | 13 +++++++++---- app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md | 1 + 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 212ee492..38d26598 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2158,6 +2158,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." +-- Workspace Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Workspace Name" + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" @@ -2188,6 +2191,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" -- Add Workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace" +-- Chat Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Chat Name" + -- Empty chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4019509364"] = "Empty chat" @@ -4138,12 +4144,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832 -- Preselect one of your profiles? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?" --- Chat name -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat name" - -- Please enter a value. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value." +-- Your Input +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Input" + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index 1f86dd3c..8970762e 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -399,6 +399,7 @@ public partial class Workspaces : MSGComponentBase var dialogParameters = new DialogParameters { { x => x.Message, string.Format(T("Please enter a new or edit the name for your chat '{0}':"), chat.Name) }, + { x => x.InputHeaderText, T("Chat Name") }, { x => x.UserInput, chat.Name }, { x => x.ConfirmText, T("Rename") }, { x => x.ConfirmColor, Color.Info }, @@ -433,6 +434,7 @@ public partial class Workspaces : MSGComponentBase var dialogParameters = new DialogParameters { { x => x.Message, string.Format(T("Please enter a new or edit the name for your workspace '{0}':"), workspaceName) }, + { x => x.InputHeaderText, T("Workspace Name") }, { x => x.UserInput, workspaceName }, { x => x.ConfirmText, T("Rename") }, { x => x.ConfirmColor, Color.Info }, @@ -456,6 +458,7 @@ public partial class Workspaces : MSGComponentBase var dialogParameters = new DialogParameters { { x => x.Message, T("Please name your workspace:") }, + { x => x.InputHeaderText, T("Workspace Name") }, { x => x.UserInput, string.Empty }, { x => x.ConfirmText, T("Add workspace") }, { x => x.ConfirmColor, Color.Info }, diff --git a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor index 5e4bb80c..23c0537f 100644 --- a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor +++ b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor @@ -5,7 +5,7 @@ @this.Message - + diff --git a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs index 08d3d94d..01c5be54 100644 --- a/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/SingleInputDialog.razor.cs @@ -23,6 +23,9 @@ public partial class SingleInputDialog : MSGComponentBase [Parameter] public bool AllowEmptyInput { get; set; } + + [Parameter] + public string InputHeaderText { get; set; } = string.Empty; [Parameter] public string EmptyInputErrorMessage { get; set; } = string.Empty; @@ -42,6 +45,8 @@ public partial class SingleInputDialog : MSGComponentBase #endregion + private string GetInputHeaderText => string.IsNullOrWhiteSpace(this.InputHeaderText) ? T("Your Input") : this.InputHeaderText; + private string? ValidateUserInput(string? value) { if (!this.AllowEmptyInput && string.IsNullOrWhiteSpace(value)) 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 bab0ac14..83aa176c 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 @@ -2160,6 +2160,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Chat lösche -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Bitte geben Sie einen Namen für diesen Chat ein." +-- Workspace Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Name des Arbeitsbereichs" + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "In einen Arbeitsbereich verschieben" @@ -2190,6 +2193,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Chat laden" -- Add Workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Arbeitsbereich hinzufügen" +-- Chat Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Name des Chat" + -- Empty chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4019509364"] = "Leerer Chat" @@ -4140,12 +4146,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832 -- Preselect one of your profiles? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Eines ihrer Profile vorauswählen?" --- Chat name -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat-Name" - -- Please enter a value. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Bitte geben Sie einen Wert ein." +-- Your Input +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Ihre Eingabe" + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Abbrechen" 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 75575e12..b0354e2d 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 @@ -2160,6 +2160,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" -- Please enter a chat name. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2301651387"] = "Please enter a chat name." +-- Workspace Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2446263209"] = "Workspace Name" + -- Move to workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" @@ -2190,6 +2193,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" -- Add Workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace" +-- Chat Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Chat Name" + -- Empty chat UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4019509364"] = "Empty chat" @@ -4140,12 +4146,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832 -- Preselect one of your profiles? UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?" --- Chat name -UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat name" - -- Please enter a value. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value." +-- Your Input +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Input" + -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" @@ -5714,4 +5720,3 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed w -- Delete Chat UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat" - 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 0155d75c..eb80ba14 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -7,4 +7,5 @@ - Improved the dialog for naming chats and workspaces to ensure valid inputs are entered. - Changed the configuration plugin setting name for how often to check for updates from `UpdateBehavior` to `UpdateInterval`. - Fixed a bug in various assistants where some text fields were not reset when resetting. +- Fixed the input field header in the dialog for naming chats and workspaces. - Fixed a rare chat-related bug that could occur when a workspace was not created correctly. Thank you, Naomi, for reporting this issue. \ No newline at end of file From f1c2a65450ac331545d194e781ccd03f2c30d40a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 28 Aug 2025 18:51:44 +0200 Subject: [PATCH 6/6] Made dialog parameter usage explicit with generic types (#543) --- .../Assistants/ERI/AssistantERI.razor.cs | 12 +++++----- .../Components/ChatComponent.razor.cs | 16 ++++++------- .../Settings/SettingsPanelEmbeddings.razor.cs | 4 ++-- .../Settings/SettingsPanelProviders.razor.cs | 8 +++---- .../Components/Workspaces.razor.cs | 24 +++++++++---------- .../Dialogs/PandocDialog.razor.cs | 4 ++-- .../SettingsDialogAssistantBias.razor.cs | 4 ++-- .../SettingsDialogChatTemplate.razor.cs | 4 ++-- .../SettingsDialogDataSources.razor.cs | 4 ++-- .../Settings/SettingsDialogProfiles.razor.cs | 4 ++-- .../Layout/MainLayout.razor.cs | 4 ++-- .../Tools/WorkspaceBehaviour.cs | 4 ++-- .../wwwroot/changelog/v0.9.51.md | 1 + 13 files changed, 47 insertions(+), 46 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs index 32e45306..6268b137 100644 --- a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs +++ b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor.cs @@ -473,9 +473,9 @@ public partial class AssistantERI : AssistantBaseCore if(this.selectedERIServer is null) return; - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the ERI server preset '{0}'?"), this.selectedERIServer.ServerName) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the ERI server preset '{0}'?"), this.selectedERIServer.ServerName) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete ERI server preset"), dialogParameters, DialogOptions.FULLSCREEN); @@ -827,9 +827,9 @@ public partial class AssistantERI : AssistantBaseCore ? string.Format(T("The embedding '{0}' is used in one or more retrieval processes. Are you sure you want to delete it?"), embeddingInfo.EmbeddingName) : string.Format(T("Are you sure you want to delete the embedding '{0}'?"), embeddingInfo.EmbeddingName); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", message }, + { x => x.Message, message }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Embedding"), dialogParameters, DialogOptions.FULLSCREEN); @@ -890,9 +890,9 @@ public partial class AssistantERI : AssistantBaseCore private async Task DeleteRetrievalProcess(RetrievalInfo retrievalInfo) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the retrieval process '{0}'?"), retrievalInfo.Name) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the retrieval process '{0}'?"), retrievalInfo.Name) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Retrieval Process"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 6af2a5b7..96481bc5 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -587,9 +587,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable // if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", "Are you sure you want to start a new chat? All unsaved changes will be lost." }, + { x => x.Message, "Are you sure you want to start a new chat? All unsaved changes will be lost." }, }; var dialogReference = await this.DialogService.ShowAsync("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN); @@ -695,9 +695,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY && this.hasUnsavedChanges) { - var confirmationDialogParameters = new DialogParameters + var confirmationDialogParameters = new DialogParameters { - { "Message", T("Are you sure you want to move this chat? All unsaved changes will be lost.") }, + { x => x.Message, T("Are you sure you want to move this chat? All unsaved changes will be lost.") }, }; var confirmationDialogReference = await this.DialogService.ShowAsync("Unsaved Changes", confirmationDialogParameters, DialogOptions.FULLSCREEN); @@ -706,11 +706,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable return; } - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Please select the workspace where you want to move the chat to.") }, - { "SelectedWorkspace", this.ChatThread?.WorkspaceId }, - { "ConfirmText", T("Move chat") }, + { x => x.Message, T("Please select the workspace where you want to move the chat to.") }, + { x => x.SelectedWorkspace, this.ChatThread?.WorkspaceId }, + { x => x.ConfirmText, T("Move chat") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Move Chat to Workspace"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs index 5d80211c..ec8a2316 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs @@ -90,9 +90,9 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase private async Task DeleteEmbeddingProvider(EmbeddingProvider provider) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the embedding provider '{0}'?"), provider.Name) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the embedding provider '{0}'?"), provider.Name) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index a2e650da..1af88e3e 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -96,9 +96,9 @@ public partial class SettingsPanelProviders : SettingsPanelBase [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")] private async Task DeleteLLMProvider(AIStudio.Settings.Provider provider) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the provider '{0}'?"), provider.InstanceName) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the provider '{0}'?"), provider.InstanceName) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN); @@ -114,9 +114,9 @@ public partial class SettingsPanelProviders : SettingsPanelBase } else { - var issueDialogParameters = new DialogParameters + var issueDialogParameters = new DialogParameters { - { "Message", string.Format(T("Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?"), provider.InstanceName, deleteSecretResponse.Issue) }, + { x => x.Message, string.Format(T("Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?"), provider.InstanceName, deleteSecretResponse.Issue) }, }; var issueDialogReference = await this.DialogService.ShowAsync(T("Delete LLM Provider"), issueDialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index 8970762e..7fc51877 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -315,9 +315,9 @@ public partial class Workspaces : MSGComponentBase // Check if the chat has unsaved changes: if (switchToChat && await MessageBus.INSTANCE.SendMessageUseFirstResult(this, Event.HAS_CHAT_UNSAVED_CHANGES)) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Are you sure you want to load another chat? All unsaved changes will be lost.") }, + { x => x.Message, T("Are you sure you want to load another chat? All unsaved changes will be lost.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Load Chat"), dialogParameters, DialogOptions.FULLSCREEN); @@ -356,10 +356,10 @@ public partial class Workspaces : MSGComponentBase if (askForConfirmation) { var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(chat.WorkspaceId); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { { - "Message", (chat.WorkspaceId == Guid.Empty) switch + x => x.Message, (chat.WorkspaceId == Guid.Empty) switch { true => string.Format(T("Are you sure you want to delete the temporary chat '{0}'?"), chat.Name), false => string.Format(T("Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"), chat.Name, workspaceName), @@ -492,9 +492,9 @@ public partial class Workspaces : MSGComponentBase // Determine how many chats are in the workspace: var chatCount = Directory.EnumerateDirectories(workspacePath).Count(); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace."), workspaceName, chatCount) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace."), workspaceName, chatCount) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Workspace"), dialogParameters, DialogOptions.FULLSCREEN); @@ -512,11 +512,11 @@ public partial class Workspaces : MSGComponentBase if (chat is null) return; - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Please select the workspace where you want to move the chat to.") }, - { "SelectedWorkspace", chat.WorkspaceId }, - { "ConfirmText", T("Move chat") }, + { x => x.Message, T("Please select the workspace where you want to move the chat to.") }, + { x => x.SelectedWorkspace, chat.WorkspaceId }, + { x => x.ConfirmText, T("Move chat") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Move Chat to Workspace"), dialogParameters, DialogOptions.FULLSCREEN); @@ -559,9 +559,9 @@ public partial class Workspaces : MSGComponentBase // Check if the chat has unsaved changes: if (await MessageBus.INSTANCE.SendMessageUseFirstResult(this, Event.HAS_CHAT_UNSAVED_CHANGES)) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Are you sure you want to create a another chat? All unsaved changes will be lost.") }, + { x => x.Message, T("Are you sure you want to create a another chat? All unsaved changes will be lost.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Create Chat"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs index ae048016..98716f14 100644 --- a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs @@ -83,9 +83,9 @@ public partial class PandocDialog : MSGComponentBase private async Task RejectLicense() { var message = T("Pandoc is open-source and free, but if you reject its license, you can't install it and some MindWork AI Studio features will be limited (like the integration of Office files) or unavailable (like the generation of Office files). You can change your decision anytime. Are you sure you want to reject the license?"); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", message }, + { x => x.Message, message }, }; var dialogReference = await this.DialogService.ShowAsync(T("Reject Pandoc's Licence"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor.cs index 27686673..aeabdea6 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor.cs @@ -8,9 +8,9 @@ public partial class SettingsDialogAssistantBias : SettingsDialogBase private async Task ResetBiasOfTheDayHistory() { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again.") }, + { x => x.Message, T("Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Reset your bias-of-the-day statistics"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs index 73ed5fa5..93084866 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogChatTemplate.razor.cs @@ -82,9 +82,9 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase private async Task DeleteChatTemplate(ChatTemplate chatTemplate) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the chat template '{0}'?"), chatTemplate.Name) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the chat template '{0}'?"), chatTemplate.Name) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Chat Template"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs index 2c2eff67..1170de67 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs @@ -151,9 +151,9 @@ public partial class SettingsDialogDataSources : SettingsDialogBase private async Task DeleteDataSource(IDataSource dataSource) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the data source '{0}' of type {1}?"), dataSource.Name, dataSource.Type.GetDisplayName()) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the data source '{0}' of type {1}?"), dataSource.Name, dataSource.Type.GetDisplayName()) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Data Source"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs index acb5e5a6..6547257c 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogProfiles.razor.cs @@ -51,9 +51,9 @@ public partial class SettingsDialogProfiles : SettingsDialogBase private async Task DeleteProfile(Profile profile) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", string.Format(T("Are you sure you want to delete the profile '{0}'?"), profile.Name) }, + { x => x.Message, string.Format(T("Are you sure you want to delete the profile '{0}'?"), profile.Name) }, }; var dialogReference = await this.DialogService.ShowAsync(T("Delete Profile"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index 079b4161..840e8608 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -306,9 +306,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan { if (await MessageBus.INSTANCE.SendMessageUseFirstResult(this, Event.HAS_CHAT_UNSAVED_CHANGES)) { - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { - { "Message", T("Are you sure you want to leave the chat page? All unsaved changes will be lost.") }, + { x => x.Message, T("Are you sure you want to leave the chat page? All unsaved changes will be lost.") }, }; var dialogReference = await this.DialogService.ShowAsync(T("Leave Chat Page"), dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs index 66d665ca..1eab21bc 100644 --- a/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs +++ b/app/MindWork AI Studio/Tools/WorkspaceBehaviour.cs @@ -121,10 +121,10 @@ public static class WorkspaceBehaviour if (askForConfirmation) { var workspaceName = await LoadWorkspaceName(chat.WorkspaceId); - var dialogParameters = new DialogParameters + var dialogParameters = new DialogParameters { { - "Message", (chat.WorkspaceId == Guid.Empty) switch + x => x.Message, (chat.WorkspaceId == Guid.Empty) switch { true => TB($"Are you sure you want to delete the temporary chat '{chat.Name}'?"), false => TB($"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?"), 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 eb80ba14..2cc6172f 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -5,6 +5,7 @@ - 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. - Improved the dialog for naming chats and workspaces to ensure valid inputs are entered. +- Improved the dialog invocation by making parameter provision more robust. - Changed the configuration plugin setting name for how often to check for updates from `UpdateBehavior` to `UpdateInterval`. - Fixed a bug in various assistants where some text fields were not reset when resetting. - Fixed the input field header in the dialog for naming chats and workspaces.