From 5d3836441ba4cc2eb0eccef6e801248037510bcf Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 10 Mar 2025 15:44:03 +0100 Subject: [PATCH 01/31] Removed unused class --- app/MindWork AI Studio/Chat/Workspace.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 app/MindWork AI Studio/Chat/Workspace.cs diff --git a/app/MindWork AI Studio/Chat/Workspace.cs b/app/MindWork AI Studio/Chat/Workspace.cs deleted file mode 100644 index 0dad2b9b..00000000 --- a/app/MindWork AI Studio/Chat/Workspace.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AIStudio.Chat; - -/// -/// Data about a workspace. -/// -/// The name of the workspace. -public sealed class Workspace(string name) -{ - public string Name { get; set; } = name; - - public List Threads { get; set; } = new(); -} \ No newline at end of file From 0d25e96bf4f8b556794faf9109abb0c97cb1ef58 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 10 Mar 2025 15:44:55 +0100 Subject: [PATCH 02/31] Added a feature flag for the plugin system --- app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs | 2 ++ .../Settings/DataModel/PreviewFeaturesExtensions.cs | 1 + .../Settings/DataModel/PreviewVisibilityExtensions.cs | 1 + app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md | 1 + 4 files changed, 5 insertions(+) diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs index 825e8037..ff642a0a 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs @@ -8,4 +8,6 @@ public enum PreviewFeatures // PRE_WRITER_MODE_2024, PRE_RAG_2024, + + PRE_PLUGINS_2025, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs index 133d6538..2eb25587 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs @@ -6,6 +6,7 @@ public static class PreviewFeaturesExtensions { PreviewFeatures.PRE_WRITER_MODE_2024 => "Writer Mode: Experiments about how to write long texts using AI", PreviewFeatures.PRE_RAG_2024 => "RAG: Preview of our RAG implementation where you can refer your files or integrate enterprise data within your company", + PreviewFeatures.PRE_PLUGINS_2025 => "Plugins: Preview of our plugin system where you can extend the functionality of the app", _ => "Unknown preview feature" }; diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs index f80939f6..b0f07716 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs @@ -25,6 +25,7 @@ public static IList GetPreviewFeatures(this PreviewVisibility v if (visibility >= PreviewVisibility.EXPERIMENTAL) { features.Add(PreviewFeatures.PRE_WRITER_MODE_2024); + features.Add(PreviewFeatures.PRE_PLUGINS_2025); } return features; diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md index 6b27d3ea..c342e9b2 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md @@ -1 +1,2 @@ # v0.9.33, build 208 (2025-03-xx xx:xx UTC) +- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. \ No newline at end of file From 37562d37f6df0b1585b68304fe0ee811d5c6f71a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 10 Mar 2025 15:45:20 +0100 Subject: [PATCH 03/31] Added the Lua library we use for the plugin system to the about page --- app/MindWork AI Studio/MindWork AI Studio.csproj | 1 + app/MindWork AI Studio/Pages/About.razor | 1 + app/MindWork AI Studio/packages.lock.json | 6 ++++++ app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 21c02959..0ddebba9 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -47,6 +47,7 @@ + diff --git a/app/MindWork AI Studio/Pages/About.razor b/app/MindWork AI Studio/Pages/About.razor index 74492c4a..282e5e72 100644 --- a/app/MindWork AI Studio/Pages/About.razor +++ b/app/MindWork AI Studio/Pages/About.razor @@ -108,6 +108,7 @@ + diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index 22246176..4a1ca441 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -22,6 +22,12 @@ "resolved": "1.11.74", "contentHash": "q0wRGbegtr4sZXjCNoV3OeRLTOcTNJQKiO9etNVSKPoTo33unmSK8Ahg36C4jIg/Hd3aw8YnTQjtKpBy+wlOpg==" }, + "LuaCSharp": { + "type": "Direct", + "requested": "[0.4.2, )", + "resolved": "0.4.2", + "contentHash": "wS0hp7EFx+llJ/U/7Ykz4FSmQf8DH4mNejwo5/h1KuFyguzGZbKhTO22X54pXnuqa5cIKfEfQ29dluHHnCX05Q==" + }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", "requested": "[9.0.2, )", diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md index c342e9b2..fe1bf3f2 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md @@ -1,2 +1,3 @@ # v0.9.33, build 208 (2025-03-xx xx:xx UTC) -- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. \ No newline at end of file +- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. +- Added the Lua library we use for the plugin system to the about page. \ No newline at end of file From a45649d3d1b680fa5da802bf3fbd12f36b8325e5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 11 Mar 2025 19:19:17 +0100 Subject: [PATCH 04/31] Added handling for default values for methods --- .../UsageAnalyzers/EmptyStringAnalyzer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs index f6cc65b7..5092d436 100644 --- a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs +++ b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/EmptyStringAnalyzer.cs @@ -46,6 +46,9 @@ private static void AnalyzeEmptyStringLiteral(SyntaxNodeAnalysisContext context) if (IsInConstContext(stringLiteral)) return; + if (IsInParameterDefaultValue(stringLiteral)) + return; + var diagnostic = Diagnostic.Create(RULE, stringLiteral.GetLocation()); context.ReportDiagnostic(diagnostic); } @@ -65,4 +68,21 @@ private static bool IsInConstContext(LiteralExpressionSyntax stringLiteral) _ => false }; } + + private static bool IsInParameterDefaultValue(LiteralExpressionSyntax stringLiteral) + { + // Prüfen, ob das String-Literal Teil eines Parameter-Defaults ist + var parameter = stringLiteral.FirstAncestorOrSelf(); + if (parameter is null) + return false; + + // Überprüfen, ob das String-Literal im Default-Wert des Parameters verwendet wird + if (parameter.Default is not null && + parameter.Default.Value == stringLiteral) + { + return true; + } + + return false; + } } \ No newline at end of file From f3c9ff52bfe4e98e8a4744b91f54dd3f17c6f778 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 14 Mar 2025 21:47:32 +0100 Subject: [PATCH 05/31] Added GetAllEnumValues to common tools --- app/MindWork AI Studio/Tools/CommonTools.cs | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/CommonTools.cs diff --git a/app/MindWork AI Studio/Tools/CommonTools.cs b/app/MindWork AI Studio/Tools/CommonTools.cs new file mode 100644 index 00000000..26150880 --- /dev/null +++ b/app/MindWork AI Studio/Tools/CommonTools.cs @@ -0,0 +1,22 @@ +using System.Text; + +namespace AIStudio.Tools; + +public static class CommonTools +{ + /// + /// Get all the values (the names) of an enum as a string, separated by commas. + /// + /// The enum type to get the values of. + /// The values to exclude from the result. + /// The values of the enum as a string, separated by commas. + public static string GetAllEnumValues(params TEnum[] exceptions) where TEnum : struct, Enum + { + var sb = new StringBuilder(); + foreach (var value in Enum.GetValues()) + if(!exceptions.Contains(value)) + sb.Append(value).Append(", "); + + return sb.ToString(); + } +} \ No newline at end of file From 28009e185add2d018b9a12e349ab19beca5a3ae6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 14 Mar 2025 21:56:55 +0100 Subject: [PATCH 06/31] Implemented the plugin base system --- .../Tools/PluginSystem/NoModuleLoader.cs | 20 ++ .../Tools/PluginSystem/NoPlugin.cs | 10 + .../Tools/PluginSystem/PluginBase.cs | 327 ++++++++++++++++++ .../Tools/PluginSystem/PluginCategory.cs | 33 ++ .../PluginSystem/PluginCategoryExtensions.cs | 38 ++ .../Tools/PluginSystem/PluginFactory.cs | 40 +++ .../Tools/PluginSystem/PluginState.cs | 8 + .../Tools/PluginSystem/PluginTargetGroup.cs | 20 ++ .../PluginTargetGroupExtensions.cs | 25 ++ .../Tools/PluginSystem/PluginType.cs | 10 + .../PluginSystem/PluginTypeExtensions.cs | 13 + .../Tools/PluginSystem/PluginVersion.cs | 90 +++++ 12 files changed, 634 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/NoModuleLoader.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginCategory.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginCategoryExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroup.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroupExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginVersion.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoModuleLoader.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoModuleLoader.cs new file mode 100644 index 00000000..d40d2237 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoModuleLoader.cs @@ -0,0 +1,20 @@ +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +/// +/// This Lua module loader does not load any modules. +/// +public sealed class NoModuleLoader : ILuaModuleLoader +{ + #region Implementation of ILuaModuleLoader + + public bool Exists(string moduleName) => false; + + public ValueTask LoadAsync(string moduleName, CancellationToken cancellationToken = default) + { + return ValueTask.FromResult(new LuaModule()); + } + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs new file mode 100644 index 00000000..ab08241f --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs @@ -0,0 +1,10 @@ +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a plugin that could not be loaded. +/// +/// The Lua state that the plugin was loaded into. +/// The error message that occurred while parsing the plugin. +public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(state, PluginType.NONE, parsingError); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs new file mode 100644 index 00000000..b48fab7f --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -0,0 +1,327 @@ +using Lua; + +// ReSharper disable MemberCanBePrivate.Global +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents the base of any AI Studio plugin. +/// +public abstract class PluginBase +{ + private readonly string parseError; + + protected readonly LuaState state; + protected readonly Guid pluginId; + protected readonly string pluginName; + protected readonly PluginType pluginType; + protected readonly string pluginDescription; + protected readonly PluginVersion pluginVersion; + protected readonly string[] pluginAuthors; + protected readonly string supportContact; + protected readonly string sourceURL; + protected readonly PluginCategory[] pluginCategories; + protected readonly PluginTargetGroup[] pluginTargetGroups; + + private readonly bool isInitialized; + private bool isValid; + + protected PluginBase(LuaState state, PluginType type, string parseError = "") + { + this.state = state; + this.pluginType = type; + this.pluginId = this.Id(); + this.pluginName = this.Name(); + this.pluginDescription = this.Description(); + this.pluginVersion = this.Version(); + this.pluginAuthors = this.Authors(); + this.supportContact = this.SupportContact(); + this.sourceURL = this.SourceURL(); + this.pluginCategories = this.Categories(); + this.pluginTargetGroups = this.TargetGroups(); + this.parseError = parseError; + + // For security reasons, we don't want to allow the plugin to load modules: + this.state.ModuleLoader = new NoModuleLoader(); + + // + // Check if the plugin is valid: + // + if(!string.IsNullOrWhiteSpace(this.parseError)) + this.isValid = false; + + if(this is NoPlugin) + this.isValid = false; + + this.isInitialized = true; + } + + /// + /// Checks if the plugin is valid. + /// + /// The state of the plugin, which may contain an error message. + public PluginState IsValid() + { + if(!string.IsNullOrWhiteSpace(this.parseError)) + { + this.isValid = false; + return new(false, this.parseError); + } + + if(this is NoPlugin) + { + this.isValid = false; + return new(false, "Plugin is not valid."); + } + + if(this.Id() == Guid.Empty) + { + this.isValid = false; + return new(false, "The field ID does not exist, is empty, or is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)."); + } + + if(string.IsNullOrWhiteSpace(this.Name())) + { + this.isValid = false; + return new(false, "The field NAME does not exist, is empty, or is not a valid string."); + } + + if(string.IsNullOrWhiteSpace(this.Description())) + { + this.isValid = false; + return new(false, "The field DESCRIPTION does not exist, is empty, or is not a valid string."); + } + + if(this.Version() == PluginVersion.NONE) + { + this.isValid = false; + return new(false, "The field VERSION does not exist, is empty, or is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."); + } + + if(this.pluginType == PluginType.NONE) + { + this.isValid = false; + return new(false, $"The field TYPE does not exist, is empty, or is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues(PluginType.NONE)}."); + } + + if(this.Authors().Length == 0) + { + this.isValid = false; + return new(false, "The table AUTHORS does not exist, is empty, or is not a valid table of strings."); + } + + if(string.IsNullOrWhiteSpace(this.SupportContact())) + { + this.isValid = false; + return new(false, "The field SUPPORT_CONTACT does not exist, is empty, or is not a valid string."); + } + + if(string.IsNullOrWhiteSpace(this.SourceURL())) + { + this.isValid = false; + return new(false, "The field SOURCE_URL does not exist, is empty, or is not a valid string. Additional, it must start with 'http://' or 'https://'."); + } + + if(this.Categories().Length == 0) + { + this.isValid = false; + return new(false, $"The table CATEGORIES does not exist, is empty, or is not a valid table of strings. Valid categories are: {CommonTools.GetAllEnumValues(PluginCategory.NONE)}."); + } + + if(this.TargetGroups().Length == 0) + { + this.isValid = false; + return new(false, $"The table TARGET_GROUPS does not exist, is empty, or is not a valid table of strings. Valid target groups are: {CommonTools.GetAllEnumValues(PluginTargetGroup.NONE)}."); + } + + this.isValid = true; + return new(true, string.Empty); + } + + /// + /// Returns the intended target groups for the plugin. + /// + /// The target groups. + public PluginTargetGroup[] TargetGroups() + { + if(this.isInitialized) + return this.pluginTargetGroups; + + if(!this.isValid) + return []; + + if (!this.state.Environment["TARGET_GROUPS"].TryRead(out var targetGroups)) + return []; + + var targetGroupList = new List(); + foreach(var luaTargetGroup in targetGroups.GetArraySpan()) + if(luaTargetGroup.TryRead(out var targetGroupName)) + if(Enum.TryParse(targetGroupName, out var targetGroup) && targetGroup != PluginTargetGroup.NONE) + targetGroupList.Add(targetGroup); + + return targetGroupList.ToArray(); + } + + /// + /// Returns the plugin categories. + /// + /// The plugin categories. + public PluginCategory[] Categories() + { + if(this.isInitialized) + return this.pluginCategories; + + if(!this.isValid) + return []; + + if (!this.state.Environment["CATEGORIES"].TryRead(out var categories)) + return []; + + var categoryList = new List(); + foreach(var luaCategory in categories.GetArraySpan()) + if(luaCategory.TryRead(out var categoryName)) + if(Enum.TryParse(categoryName, out var category) && category != PluginCategory.NONE) + categoryList.Add(category); + + return categoryList.ToArray(); + } + + /// + /// Returns the source URL of the plugin. + /// + /// The source URL. + public string SourceURL() + { + if(this.isInitialized) + return this.sourceURL; + + if(!this.isValid) + return string.Empty; + + if (!this.state.Environment["SOURCE_URL"].TryRead(out var url)) + return string.Empty; + + if(!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + return string.Empty; + + return url; + } + + /// + /// Returns the support contact of the plugin. + /// + /// The support contact. + public string SupportContact() + { + if(this.isInitialized) + return this.supportContact; + + if(!this.isValid) + return string.Empty; + + if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out var contact)) + return string.Empty; + + return contact; + } + + /// + /// Returns the ID of the plugin. + /// + /// The plugin ID. + public Guid Id() + { + if(this.isInitialized) + return this.pluginId; + + if(!this.isValid) + return Guid.Empty; + + if (!this.state.Environment["ID"].TryRead(out var idText)) + return Guid.Empty; + + if (!Guid.TryParse(idText, out var id)) + return Guid.Empty; + + return id; + } + + /// + /// Returns the name of the plugin. + /// + /// The plugin name. + public string Name() + { + if(this.isInitialized) + return this.pluginName; + + if(!this.isValid) + return string.Empty; + + if (!this.state.Environment["NAME"].TryRead(out var name)) + return string.Empty; + + return name; + } + + /// + /// Returns the description of the plugin. + /// + /// The plugin description. + public string Description() + { + if(this.isInitialized) + return this.pluginDescription; + + if(!this.isValid) + return string.Empty; + + if (!this.state.Environment["DESCRIPTION"].TryRead(out var description)) + return string.Empty; + + return description; + } + + /// + /// Returns the version of the plugin. + /// + /// The plugin version. + public PluginVersion Version() + { + if(this.isInitialized) + return this.pluginVersion; + + if(!this.isValid) + return PluginVersion.NONE; + + if (!this.state.Environment["VERSION"].TryRead(out var versionText)) + return PluginVersion.NONE; + + if (!PluginVersion.TryParse(versionText, out var version)) + return PluginVersion.NONE; + + return version; + } + + /// + /// Returns the authors of the plugin. + /// + /// The plugin authors. + public string[] Authors() + { + if(this.isInitialized) + return this.pluginAuthors; + + if (!this.isValid) + return []; + + if (!this.state.Environment["AUTHORS"].TryRead(out var authors)) + return []; + + var authorList = new List(); + foreach(var author in authors.GetArraySpan()) + if(author.TryRead(out var authorName)) + authorList.Add(authorName); + + return authorList.ToArray(); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginCategory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginCategory.cs new file mode 100644 index 00000000..00afcd0e --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginCategory.cs @@ -0,0 +1,33 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum PluginCategory +{ + NONE, + CORE, + + BUSINESS, + INDUSTRY, + UTILITY, + SOFTWARE_DEVELOPMENT, + GAMING, + EDUCATION, + ENTERTAINMENT, + SOCIAL, + SHOPPING, + TRAVEL, + HEALTH, + FITNESS, + FOOD, + PARTY, + SPORTS, + NEWS, + WEATHER, + MUSIC, + POLITICAL, + SCIENCE, + TECHNOLOGY, + ART, + FICTION, + WRITING, + CONTENT_CREATION, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginCategoryExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginCategoryExtensions.cs new file mode 100644 index 00000000..35303c06 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginCategoryExtensions.cs @@ -0,0 +1,38 @@ +namespace AIStudio.Tools.PluginSystem; + +public static class PluginCategoryExtensions +{ + public static string GetName(this PluginCategory type) => type switch + { + PluginCategory.NONE => "None", + PluginCategory.CORE => "AI Studio Core", + + PluginCategory.BUSINESS => "Business", + PluginCategory.INDUSTRY => "Industry", + PluginCategory.UTILITY => "Utility", + PluginCategory.SOFTWARE_DEVELOPMENT => "Software Development", + PluginCategory.GAMING => "Gaming", + PluginCategory.EDUCATION => "Education", + PluginCategory.ENTERTAINMENT => "Entertainment", + PluginCategory.SOCIAL => "Social", + PluginCategory.SHOPPING => "Shopping", + PluginCategory.TRAVEL => "Travel", + PluginCategory.HEALTH => "Health", + PluginCategory.FITNESS => "Fitness", + PluginCategory.FOOD => "Food", + PluginCategory.PARTY => "Party", + PluginCategory.SPORTS => "Sports", + PluginCategory.NEWS => "News", + PluginCategory.WEATHER => "Weather", + PluginCategory.MUSIC => "Music", + PluginCategory.POLITICAL => "Political", + PluginCategory.SCIENCE => "Science", + PluginCategory.TECHNOLOGY => "Technology", + PluginCategory.ART => "Art", + PluginCategory.FICTION => "Fiction", + PluginCategory.WRITING => "Writing", + PluginCategory.CONTENT_CREATION => "Content Creation", + + _ => "Unknown plugin category", + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs new file mode 100644 index 00000000..0f1ce8d9 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -0,0 +1,40 @@ +using System.Text; + +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +public static class PluginFactory +{ + public static async Task LoadAll() + { + + } + + public static async Task Load(string code, CancellationToken cancellationToken = default) + { + var state = LuaState.Create(); + + try + { + await state.DoStringAsync(code, cancellationToken: cancellationToken); + } + catch (LuaParseException e) + { + return new NoPlugin(state, $"Was not able to parse the plugin: {e.Message}"); + } + + if (!state.Environment["TYPE"].TryRead(out var typeText)) + return new NoPlugin(state, "TYPE does not exist or is not a valid string."); + + if (!Enum.TryParse(typeText, out var type)) + return new NoPlugin(state, $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + + return type switch + { + PluginType.LANGUAGE => new PluginLanguage(state, type), + + _ => new NoPlugin(state, "This plugin type is not supported yet. Please try again with a future version of AI Studio.") + }; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs new file mode 100644 index 00000000..60b11790 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents the state of a plugin. +/// +/// True, when the plugin is valid. +/// When the plugin is invalid, this contains the error message. +public readonly record struct PluginState(bool Valid, string Message); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroup.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroup.cs new file mode 100644 index 00000000..102aa857 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroup.cs @@ -0,0 +1,20 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum PluginTargetGroup +{ + NONE, + + EVERYONE, + CHILDREN, + TEENAGERS, + STUDENTS, + ADULTS, + + INDUSTRIAL_WORKERS, + OFFICE_WORKERS, + BUSINESS_PROFESSIONALS, + SOFTWARE_DEVELOPERS, + SCIENTISTS, + TEACHERS, + ARTISTS, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroupExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroupExtensions.cs new file mode 100644 index 00000000..7a14123f --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginTargetGroupExtensions.cs @@ -0,0 +1,25 @@ +namespace AIStudio.Tools.PluginSystem; + +public static class PluginTargetGroupExtensions +{ + public static string Name(this PluginTargetGroup group) => group switch + { + PluginTargetGroup.NONE => "No target group", + + PluginTargetGroup.EVERYONE => "Everyone", + PluginTargetGroup.CHILDREN => "Children", + PluginTargetGroup.TEENAGERS => "Teenagers", + PluginTargetGroup.STUDENTS => "Students", + PluginTargetGroup.ADULTS => "Adults", + + PluginTargetGroup.INDUSTRIAL_WORKERS => "Industrial workers", + PluginTargetGroup.OFFICE_WORKERS => "Office workers", + PluginTargetGroup.BUSINESS_PROFESSIONALS => "Business professionals", + PluginTargetGroup.SOFTWARE_DEVELOPERS => "Software developers", + PluginTargetGroup.SCIENTISTS => "Scientists", + PluginTargetGroup.TEACHERS => "Teachers", + PluginTargetGroup.ARTISTS => "Artists", + + _ => "Unknown target group", + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs new file mode 100644 index 00000000..f897eb6e --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs @@ -0,0 +1,10 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum PluginType +{ + NONE, + + LANGUAGE, + ASSISTANT, + CONFIGURATION, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs new file mode 100644 index 00000000..9ef0535c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs @@ -0,0 +1,13 @@ +namespace AIStudio.Tools.PluginSystem; + +public static class PluginTypeExtensions +{ + public static string GetName(this PluginType type) => type switch + { + PluginType.LANGUAGE => "Language plugin", + PluginType.ASSISTANT => "Assistant plugin", + PluginType.CONFIGURATION => "Configuration plugin", + + _ => "Unknown plugin type", + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginVersion.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginVersion.cs new file mode 100644 index 00000000..d5507a56 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginVersion.cs @@ -0,0 +1,90 @@ +// ReSharper disable MemberCanBePrivate.Global +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a version number for a plugin. +/// +/// The major version number. +/// The minor version number. +/// The patch version number. +public readonly record struct PluginVersion(int Major, int Minor, int Patch) : IComparable +{ + /// + /// Represents no version number. + /// + public static readonly PluginVersion NONE = new(0, 0, 0); + + /// + /// Tries to parse the input string as a plugin version number. + /// + /// The input string to parse. + /// The parsed version number. + /// True when the input string was successfully parsed; otherwise, false. + public static bool TryParse(string input, out PluginVersion version) + { + try + { + version = Parse(input); + return true; + } + catch + { + version = NONE; + return false; + } + } + + /// + /// Parses the input string as a plugin version number. + /// + /// The input string to parse. + /// The parsed version number. + /// The input string is not in the correct format. + public static PluginVersion Parse(string input) + { + var segments = input.Split('.'); + if (segments.Length != 3) + throw new FormatException("The input string must be in the format 'major.minor.patch'."); + + var major = int.Parse(segments[0]); + var minor = int.Parse(segments[1]); + var patch = int.Parse(segments[2]); + + if(major < 0 || minor < 0 || patch < 0) + throw new FormatException("The major, minor, and patch numbers must be greater than or equal to 0."); + + return new PluginVersion(major, minor, patch); + } + + /// + /// Converts the plugin version number to a string in the format 'major.minor.patch'. + /// + /// The plugin version number as a string. + public override string ToString() => $"{this.Major}.{this.Minor}.{this.Patch}"; + + /// + /// Compares the plugin version number to another plugin version number. + /// + /// The other plugin version number to compare to. + /// A value indicating the relative order of the plugin version numbers. + public int CompareTo(PluginVersion other) + { + var majorCompare = this.Major.CompareTo(other.Major); + if (majorCompare != 0) + return majorCompare; + + var minorCompare = this.Minor.CompareTo(other.Minor); + if (minorCompare != 0) + return minorCompare; + + return this.Patch.CompareTo(other.Patch); + } + + public static bool operator >(PluginVersion left, PluginVersion right) => left.CompareTo(right) > 0; + + public static bool operator <(PluginVersion left, PluginVersion right) => left.CompareTo(right) < 0; + + public static bool operator >=(PluginVersion left, PluginVersion right) => left.CompareTo(right) >= 0; + + public static bool operator <=(PluginVersion left, PluginVersion right) => left.CompareTo(right) <= 0; +} \ No newline at end of file From cba651d034b9c5076edbece026c51f98cacc04cd Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 15 Mar 2025 21:34:46 +0100 Subject: [PATCH 07/31] Refactored the plugin base class --- .../Tools/PluginSystem/PluginBase.cs | 550 ++++++++++-------- 1 file changed, 313 insertions(+), 237 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index b48fab7f..1ff6230a 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -8,320 +8,396 @@ namespace AIStudio.Tools.PluginSystem; /// public abstract class PluginBase { - private readonly string parseError; - + private readonly IReadOnlyCollection baseIssues; protected readonly LuaState state; - protected readonly Guid pluginId; - protected readonly string pluginName; - protected readonly PluginType pluginType; - protected readonly string pluginDescription; - protected readonly PluginVersion pluginVersion; - protected readonly string[] pluginAuthors; - protected readonly string supportContact; - protected readonly string sourceURL; - protected readonly PluginCategory[] pluginCategories; - protected readonly PluginTargetGroup[] pluginTargetGroups; + + protected List pluginIssues = []; + + /// + /// The type of this plugin. + /// + public PluginType Type { get; } + + /// + /// The ID of this plugin. + /// + public Guid Id { get; } + + /// + /// The name of this plugin. + /// + public string Name { get; } = string.Empty; + + /// + /// The description of this plugin. + /// + public string Description { get; } = string.Empty; + + /// + /// The version of this plugin. + /// + public PluginVersion Version { get; } + + /// + /// The authors of this plugin. + /// + public string[] Authors { get; } = []; - private readonly bool isInitialized; - private bool isValid; + /// + /// The support contact for this plugin. + /// + public string SupportContact { get; } = string.Empty; + + /// + /// The source URL of this plugin. + /// + public string SourceURL { get; } = string.Empty; + + /// + /// The categories of this plugin. + /// + public PluginCategory[] Categories { get; } = []; + + /// + /// The target groups of this plugin. + /// + public PluginTargetGroup[] TargetGroups { get; } = []; + + /// + /// The issues that occurred during the initialization of this plugin. + /// + public IEnumerable Issues => this.baseIssues.Concat(this.pluginIssues); + + /// + /// True, when the plugin is valid. + /// + /// + /// False means that there were issues during the initialization of the plugin. + /// Please check the Issues property for more information. + /// + public bool IsValid => this is not NoPlugin && this.baseIssues.Count == 0 && this.pluginIssues.Count == 0; protected PluginBase(LuaState state, PluginType type, string parseError = "") { this.state = state; - this.pluginType = type; - this.pluginId = this.Id(); - this.pluginName = this.Name(); - this.pluginDescription = this.Description(); - this.pluginVersion = this.Version(); - this.pluginAuthors = this.Authors(); - this.supportContact = this.SupportContact(); - this.sourceURL = this.SourceURL(); - this.pluginCategories = this.Categories(); - this.pluginTargetGroups = this.TargetGroups(); - this.parseError = parseError; - + this.Type = type; + // For security reasons, we don't want to allow the plugin to load modules: this.state.ModuleLoader = new NoModuleLoader(); - // - // Check if the plugin is valid: - // - if(!string.IsNullOrWhiteSpace(this.parseError)) - this.isValid = false; - - if(this is NoPlugin) - this.isValid = false; - - this.isInitialized = true; + var issues = new List(); + if(!string.IsNullOrWhiteSpace(parseError)) + issues.Add(parseError); + + if(this.TryInitId(out var issue, out var id)) + this.Id = id; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitName(out issue, out var name)) + this.Name = name; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitDescription(out issue, out var description)) + this.Description = description; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitVersion(out issue, out var version)) + this.Version = version; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitAuthors(out issue, out var authors)) + this.Authors = authors; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitSupportContact(out issue, out var contact)) + this.SupportContact = contact; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitSourceURL(out issue, out var url)) + this.SourceURL = url; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitCategories(out issue, out var categories)) + this.Categories = categories; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitTargetGroups(out issue, out var targetGroups)) + this.TargetGroups = targetGroups; + else if(this is not NoPlugin) + issues.Add(issue); + + this.baseIssues = issues; } + #region Initialization-related methods + /// - /// Checks if the plugin is valid. + /// Tries to read the ID of the plugin. /// - /// The state of the plugin, which may contain an error message. - public PluginState IsValid() + /// The error message, when the ID could not be read. + /// The read ID. + /// True, when the ID could be read successfully. + public bool TryInitId(out string message, out Guid id) { - if(!string.IsNullOrWhiteSpace(this.parseError)) - { - this.isValid = false; - return new(false, this.parseError); - } - - if(this is NoPlugin) + if (!this.state.Environment["ID"].TryRead(out var idText)) { - this.isValid = false; - return new(false, "Plugin is not valid."); + message = "The field ID does not exist or is not a valid string."; + id = Guid.Empty; + return false; } - if(this.Id() == Guid.Empty) - { - this.isValid = false; - return new(false, "The field ID does not exist, is empty, or is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)."); - } - - if(string.IsNullOrWhiteSpace(this.Name())) - { - this.isValid = false; - return new(false, "The field NAME does not exist, is empty, or is not a valid string."); - } - - if(string.IsNullOrWhiteSpace(this.Description())) - { - this.isValid = false; - return new(false, "The field DESCRIPTION does not exist, is empty, or is not a valid string."); - } - - if(this.Version() == PluginVersion.NONE) - { - this.isValid = false; - return new(false, "The field VERSION does not exist, is empty, or is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."); - } - - if(this.pluginType == PluginType.NONE) - { - this.isValid = false; - return new(false, $"The field TYPE does not exist, is empty, or is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues(PluginType.NONE)}."); - } - - if(this.Authors().Length == 0) + if (!Guid.TryParse(idText, out id)) { - this.isValid = false; - return new(false, "The table AUTHORS does not exist, is empty, or is not a valid table of strings."); + message = "The field ID is not a valid GUID / UUID. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)."; + id = Guid.Empty; + return false; } - if(string.IsNullOrWhiteSpace(this.SupportContact())) + if(id == Guid.Empty) { - this.isValid = false; - return new(false, "The field SUPPORT_CONTACT does not exist, is empty, or is not a valid string."); - } - - if(string.IsNullOrWhiteSpace(this.SourceURL())) - { - this.isValid = false; - return new(false, "The field SOURCE_URL does not exist, is empty, or is not a valid string. Additional, it must start with 'http://' or 'https://'."); - } - - if(this.Categories().Length == 0) - { - this.isValid = false; - return new(false, $"The table CATEGORIES does not exist, is empty, or is not a valid table of strings. Valid categories are: {CommonTools.GetAllEnumValues(PluginCategory.NONE)}."); - } - - if(this.TargetGroups().Length == 0) - { - this.isValid = false; - return new(false, $"The table TARGET_GROUPS does not exist, is empty, or is not a valid table of strings. Valid target groups are: {CommonTools.GetAllEnumValues(PluginTargetGroup.NONE)}."); + message = "The field ID is empty. The ID must be formatted in the 8-4-4-4-12 format (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)."; + return false; } - this.isValid = true; - return new(true, string.Empty); + message = string.Empty; + return true; } - + /// - /// Returns the intended target groups for the plugin. + /// Tries to read the name of the plugin. /// - /// The target groups. - public PluginTargetGroup[] TargetGroups() + /// The error message, when the name could not be read. + /// The read name. + /// True, when the name could be read successfully. + public bool TryInitName(out string message, out string name) { - if(this.isInitialized) - return this.pluginTargetGroups; - - if(!this.isValid) - return []; - - if (!this.state.Environment["TARGET_GROUPS"].TryRead(out var targetGroups)) - return []; - - var targetGroupList = new List(); - foreach(var luaTargetGroup in targetGroups.GetArraySpan()) - if(luaTargetGroup.TryRead(out var targetGroupName)) - if(Enum.TryParse(targetGroupName, out var targetGroup) && targetGroup != PluginTargetGroup.NONE) - targetGroupList.Add(targetGroup); + if (!this.state.Environment["NAME"].TryRead(out name)) + { + message = "The field NAME does not exist or is not a valid string."; + name = string.Empty; + return false; + } - return targetGroupList.ToArray(); - } + if(string.IsNullOrWhiteSpace(name)) + { + message = "The field NAME is empty. The name must be a non-empty string."; + return false; + } - /// - /// Returns the plugin categories. - /// - /// The plugin categories. - public PluginCategory[] Categories() - { - if(this.isInitialized) - return this.pluginCategories; - - if(!this.isValid) - return []; - - if (!this.state.Environment["CATEGORIES"].TryRead(out var categories)) - return []; - - var categoryList = new List(); - foreach(var luaCategory in categories.GetArraySpan()) - if(luaCategory.TryRead(out var categoryName)) - if(Enum.TryParse(categoryName, out var category) && category != PluginCategory.NONE) - categoryList.Add(category); - - return categoryList.ToArray(); + message = string.Empty; + return true; } /// - /// Returns the source URL of the plugin. + /// Tries to read the description of the plugin. /// - /// The source URL. - public string SourceURL() + /// The error message, when the description could not be read. + /// The read description. + /// True, when the description could be read successfully. + public bool TryInitDescription(out string message, out string description) { - if(this.isInitialized) - return this.sourceURL; - - if(!this.isValid) - return string.Empty; - - if (!this.state.Environment["SOURCE_URL"].TryRead(out var url)) - return string.Empty; + if (!this.state.Environment["DESCRIPTION"].TryRead(out description)) + { + message = "The field DESCRIPTION does not exist or is not a valid string."; + description = string.Empty; + return false; + } - if(!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) - return string.Empty; + if(string.IsNullOrWhiteSpace(description)) + { + message = "The field DESCRIPTION is empty. The description must be a non-empty string."; + return false; + } - return url; + message = string.Empty; + return true; } /// - /// Returns the support contact of the plugin. + /// Tries to read the version of the plugin. /// - /// The support contact. - public string SupportContact() + /// The error message, when the version could not be read. + /// The read version. + /// True, when the version could be read successfully. + public bool TryInitVersion(out string message, out PluginVersion version) { - if(this.isInitialized) - return this.supportContact; - - if(!this.isValid) - return string.Empty; + if (!this.state.Environment["VERSION"].TryRead(out var versionText)) + { + message = "The field VERSION does not exist or is not a valid string."; + version = PluginVersion.NONE; + return false; + } + + if (!PluginVersion.TryParse(versionText, out version)) + { + message = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."; + version = PluginVersion.NONE; + return false; + } - if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out var contact)) - return string.Empty; + if(version == PluginVersion.NONE) + { + message = "The field VERSION is empty. The version number must be formatted as string in the major.minor.patch format (X.X.X)."; + return false; + } - return contact; + message = string.Empty; + return true; } /// - /// Returns the ID of the plugin. + /// Tries to read the authors of the plugin. /// - /// The plugin ID. - public Guid Id() + /// The error message, when the authors could not be read. + /// The read authors. + /// True, when the authors could be read successfully. + public bool TryInitAuthors(out string message, out string[] authors) { - if(this.isInitialized) - return this.pluginId; - - if(!this.isValid) - return Guid.Empty; + if (!this.state.Environment["AUTHORS"].TryRead(out var authorsTable)) + { + authors = []; + message = "The table AUTHORS does not exist or is using an invalid syntax."; + return false; + } - if (!this.state.Environment["ID"].TryRead(out var idText)) - return Guid.Empty; + var authorList = new List(); + foreach(var author in authorsTable.GetArraySpan()) + if(author.TryRead(out var authorName)) + authorList.Add(authorName); - if (!Guid.TryParse(idText, out var id)) - return Guid.Empty; + authors = authorList.ToArray(); + if(authorList.Count == 0) + { + message = "The table AUTHORS is empty. At least one author must be specified."; + return false; + } - return id; + message = string.Empty; + return true; } - + /// - /// Returns the name of the plugin. + /// Tries to read the support contact for the plugin. /// - /// The plugin name. - public string Name() + /// The error message, when the support contact could not be read. + /// The read support contact. + /// True, when the support contact could be read successfully. + public bool TryInitSupportContact(out string message, out string contact) { - if(this.isInitialized) - return this.pluginName; + if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out contact)) + { + contact = string.Empty; + message = "The field SUPPORT_CONTACT does not exist or is not a valid string."; + return false; + } - if(!this.isValid) - return string.Empty; + if(string.IsNullOrWhiteSpace(contact)) + { + message = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string."; + return false; + } - if (!this.state.Environment["NAME"].TryRead(out var name)) - return string.Empty; - - return name; + message = string.Empty; + return true; } /// - /// Returns the description of the plugin. + /// Try to read the source URL of the plugin. /// - /// The plugin description. - public string Description() + /// The error message, when the source URL could not be read. + /// The read source URL. + /// True, when the source URL could be read successfully. + public bool TryInitSourceURL(out string message, out string url) { - if(this.isInitialized) - return this.pluginDescription; - - if(!this.isValid) - return string.Empty; - - if (!this.state.Environment["DESCRIPTION"].TryRead(out var description)) - return string.Empty; + if (!this.state.Environment["SOURCE_URL"].TryRead(out url)) + { + url = string.Empty; + message = "The field SOURCE_URL does not exist or is not a valid string."; + return false; + } + + if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + { + url = string.Empty; + message = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'."; + return false; + } - return description; + message = string.Empty; + return true; } /// - /// Returns the version of the plugin. + /// Tries to read the categories of the plugin. /// - /// The plugin version. - public PluginVersion Version() + /// The error message, when the categories could not be read. + /// The read categories. + /// True, when the categories could be read successfully. + public bool TryInitCategories(out string message, out PluginCategory[] categories) { - if(this.isInitialized) - return this.pluginVersion; - - if(!this.isValid) - return PluginVersion.NONE; + if (!this.state.Environment["CATEGORIES"].TryRead(out var categoriesTable)) + { + categories = []; + message = "The table CATEGORIES does not exist or is using an invalid syntax."; + return false; + } - if (!this.state.Environment["VERSION"].TryRead(out var versionText)) - return PluginVersion.NONE; + var categoryList = new List(); + foreach(var luaCategory in categoriesTable.GetArraySpan()) + if(luaCategory.TryRead(out var categoryName)) + if(Enum.TryParse(categoryName, out var category) && category != PluginCategory.NONE) + categoryList.Add(category); - if (!PluginVersion.TryParse(versionText, out var version)) - return PluginVersion.NONE; + categories = categoryList.ToArray(); + if(categoryList.Count == 0) + { + message = $"The table CATEGORIES is empty. At least one category is necessary. Valid categories are: {CommonTools.GetAllEnumValues(PluginCategory.NONE)}."; + return false; + } - return version; + message = string.Empty; + return true; } - + /// - /// Returns the authors of the plugin. + /// Tries to read the intended target groups for the plugin. /// - /// The plugin authors. - public string[] Authors() + /// The error message, when the target groups could not be read. + /// The read target groups. + /// True, when the target groups could be read successfully. + private bool TryInitTargetGroups(out string message, out PluginTargetGroup[] targetGroups) { - if(this.isInitialized) - return this.pluginAuthors; + if (!this.state.Environment["TARGET_GROUPS"].TryRead(out var targetGroupsTable)) + { + targetGroups = []; + message = "The table TARGET_GROUPS does not exist or is using an invalid syntax."; + return false; + } - if (!this.isValid) - return []; - - if (!this.state.Environment["AUTHORS"].TryRead(out var authors)) - return []; + var targetGroupList = new List(); + foreach(var luaTargetGroup in targetGroupsTable.GetArraySpan()) + if(luaTargetGroup.TryRead(out var targetGroupName)) + if(Enum.TryParse(targetGroupName, out var targetGroup) && targetGroup != PluginTargetGroup.NONE) + targetGroupList.Add(targetGroup); - var authorList = new List(); - foreach(var author in authors.GetArraySpan()) - if(author.TryRead(out var authorName)) - authorList.Add(authorName); + targetGroups = targetGroupList.ToArray(); + if(targetGroups.Length == 0) + { + message = "The table TARGET_GROUPS is empty or is not a valid table of strings. Valid target groups are: {CommonTools.GetAllEnumValues(PluginTargetGroup.NONE)}."; + return false; + } - return authorList.ToArray(); + message = string.Empty; + return true; } + + #endregion } \ No newline at end of file From 4d79b0ec94f7f9c18be3f35b5575982cc7613f9c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 15 Mar 2025 21:35:03 +0100 Subject: [PATCH 08/31] Added another check --- app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 0f1ce8d9..dd14b8fe 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -30,6 +30,9 @@ public static async Task Load(string code, CancellationToken cancell if (!Enum.TryParse(typeText, out var type)) return new NoPlugin(state, $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + if(type is PluginType.NONE) + return new NoPlugin(state, $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + return type switch { PluginType.LANGUAGE => new PluginLanguage(state, type), From ee5237ac44f9602cd7f8124d0881947073b2751c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 15 Mar 2025 21:35:21 +0100 Subject: [PATCH 09/31] Added the theme plugin type --- app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs | 1 + .../Tools/PluginSystem/PluginTypeExtensions.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs index f897eb6e..5730e62f 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginType.cs @@ -7,4 +7,5 @@ public enum PluginType LANGUAGE, ASSISTANT, CONFIGURATION, + THEME, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs index 9ef0535c..a26cb03c 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs @@ -7,6 +7,7 @@ public static class PluginTypeExtensions PluginType.LANGUAGE => "Language plugin", PluginType.ASSISTANT => "Assistant plugin", PluginType.CONFIGURATION => "Configuration plugin", + PluginType.THEME => "Theme plugin", _ => "Unknown plugin type", }; From 6f772e78b5f0b700474493c77e0ce54e25b8d97a Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 15 Mar 2025 21:36:48 +0100 Subject: [PATCH 10/31] Removed plugin state --- app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs deleted file mode 100644 index 60b11790..00000000 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginState.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AIStudio.Tools.PluginSystem; - -/// -/// Represents the state of a plugin. -/// -/// True, when the plugin is valid. -/// When the plugin is invalid, this contains the error message. -public readonly record struct PluginState(bool Valid, string Message); \ No newline at end of file From a87cd41d7b038fdbc422cf1d862e09715322e75e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 17 Mar 2025 10:33:46 +0100 Subject: [PATCH 11/31] Added an example plugin for language support --- .../plugin.lua | 33 +++++++++++++++++++ .../plugin.lua | 33 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 app/MindWork AI Studio/Plugins/language/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua create mode 100644 app/MindWork AI Studio/Plugins/language/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua diff --git a/app/MindWork AI Studio/Plugins/language/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/language/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua new file mode 100644 index 00000000..c8fdd3db --- /dev/null +++ b/app/MindWork AI Studio/Plugins/language/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -0,0 +1,33 @@ +-- The ID for this plugin: +ID = "43065dbc-78d0-45b7-92be-f14c2926e2dc" + +-- The name of the plugin: +NAME = "MindWork AI Studio - German / Deutsch" + +-- The description of the plugin: +DESCRIPTION = "Dieses Plugin bietet deutsche Sprachunterstützung für MindWork AI Studio." + +-- The version of the plugin: +VERSION = "1.0.0" + +-- The type of the plugin: +TYPE = "LANGUAGE" + +-- The authors of the plugin: +AUTHORS = {"MindWork AI Community"} + +-- The support contact for the plugin: +SUPPORT_CONTACT = "MindWork AI Community" + +-- The source URL for the plugin: +SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio" + +-- The categories for the plugin: +CATEGORIES = { "CORE" } + +-- The target groups for the plugin: +TARGET_GROUPS = { "EVERYONE" } + +TEXT = { + Home_LetsGetStarted = "Lass uns anfangen", +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Plugins/language/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/language/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua new file mode 100644 index 00000000..c8e835f6 --- /dev/null +++ b/app/MindWork AI Studio/Plugins/language/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -0,0 +1,33 @@ +-- The ID for this plugin: +ID = "97dfb1ba-50c4-4440-8dfa-6575daf543c8" + +-- The name of the plugin: +NAME = "MindWork AI Studio - US English" + +-- The description of the plugin: +DESCRIPTION = "This plugin provides US English language support for MindWork AI Studio." + +-- The version of the plugin: +VERSION = "1.0.0" + +-- The type of the plugin: +TYPE = "LANGUAGE" + +-- The authors of the plugin: +AUTHORS = {"MindWork AI Community"} + +-- The support contact for the plugin: +SUPPORT_CONTACT = "MindWork AI Community" + +-- The source URL for the plugin: +SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio" + +-- The categories for the plugin: +CATEGORIES = { "CORE" } + +-- The target groups for the plugin: +TARGET_GROUPS = { "EVERYONE" } + +TEXT = { + Home_LetsGetStarted = "Let's get started", +} \ No newline at end of file From abeee2408f17d160242e8c11272cb3762e9c4fd6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 18 Mar 2025 13:13:41 +0100 Subject: [PATCH 12/31] Load some default Lua libraries --- app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index 1ff6230a..1553f74a 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -1,4 +1,5 @@ using Lua; +using Lua.Standard; // ReSharper disable MemberCanBePrivate.Global namespace AIStudio.Tools.PluginSystem; @@ -85,6 +86,14 @@ protected PluginBase(LuaState state, PluginType type, string parseError = "") // For security reasons, we don't want to allow the plugin to load modules: this.state.ModuleLoader = new NoModuleLoader(); + // Add some useful libraries: + this.state.OpenModuleLibrary(); + this.state.OpenStringLibrary(); + this.state.OpenTableLibrary(); + this.state.OpenMathLibrary(); + this.state.OpenBitwiseLibrary(); + this.state.OpenCoroutineLibrary(); + var issues = new List(); if(!string.IsNullOrWhiteSpace(parseError)) issues.Add(parseError); From a845cff7e28e5ddf596d4ca05750b1778a4ae180 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 20 Mar 2025 16:19:00 +0100 Subject: [PATCH 13/31] Added the logger factory for static classes --- app/MindWork AI Studio/Program.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 61261b16..cc37d9cd 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -22,6 +22,7 @@ internal sealed class Program public static Encryption ENCRYPTION = null!; public static string API_TOKEN = null!; public static IServiceProvider SERVICE_PROVIDER = null!; + public static ILoggerFactory LOGGER_FACTORY = null!; public static async Task Main(string[] args) { @@ -147,6 +148,9 @@ public static async Task Main(string[] args) // Execute the builder to get the app: var app = builder.Build(); + // Get the logging factory for e.g., static classes: + LOGGER_FACTORY = app.Services.GetRequiredService(); + // Get a program logger: var programLogger = app.Services.GetRequiredService>(); programLogger.LogInformation("Starting the AI Studio server."); From cc26926ab4ee153eae882a890fc711fda75b11e2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 20 Mar 2025 16:19:46 +0100 Subject: [PATCH 14/31] Embed all internal plugins --- app/MindWork AI Studio/MindWork AI Studio.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 0ddebba9..767453e3 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -40,8 +40,8 @@ - + From 8a4031ed767a1730d66493a2188c461c715853a3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 20 Mar 2025 16:20:11 +0100 Subject: [PATCH 15/31] Updated --- .../de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua | 2 +- .../contentHome.lua | 3 +++ .../en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) rename app/MindWork AI Studio/Plugins/{language => languages}/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua (98%) create mode 100644 app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua rename app/MindWork AI Studio/Plugins/{language => languages}/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua (92%) diff --git a/app/MindWork AI Studio/Plugins/language/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 similarity index 98% rename from app/MindWork AI Studio/Plugins/language/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua rename to app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index c8fdd3db..10836117 100644 --- a/app/MindWork AI Studio/Plugins/language/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 @@ -28,6 +28,6 @@ CATEGORIES = { "CORE" } -- The target groups for the plugin: TARGET_GROUPS = { "EVERYONE" } -TEXT = { +CONTENT = { Home_LetsGetStarted = "Lass uns anfangen", } \ No newline at end of file diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua new file mode 100644 index 00000000..d1805fd0 --- /dev/null +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua @@ -0,0 +1,3 @@ +CONTENT_HOME = { + LetsGetStarted = "Let's get started", +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Plugins/language/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 similarity index 92% rename from app/MindWork AI Studio/Plugins/language/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua rename to app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index c8e835f6..ab23db62 100644 --- a/app/MindWork AI Studio/Plugins/language/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 @@ -1,3 +1,5 @@ +require("contentHome") + -- The ID for this plugin: ID = "97dfb1ba-50c4-4440-8dfa-6575daf543c8" @@ -28,6 +30,6 @@ CATEGORIES = { "CORE" } -- The target groups for the plugin: TARGET_GROUPS = { "EVERYONE" } -TEXT = { - Home_LetsGetStarted = "Let's get started", +CONTENT = { + HOME = CONTENT_HOME, } \ No newline at end of file From c679e17784f0a7d719197ac5f5481b0a026428da Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 20 Mar 2025 16:21:49 +0100 Subject: [PATCH 16/31] Ensure deployment of internal plugins --- .../Layout/MainLayout.razor.cs | 4 + .../Tools/PluginSystem/InternalPlugin.cs | 7 ++ .../Tools/PluginSystem/InternalPluginData.cs | 8 ++ .../PluginSystem/InternalPluginExtensions.cs | 12 +++ .../Tools/PluginSystem/PluginFactory.cs | 81 ++++++++++++++++++- .../PluginSystem/PluginTypeExtensions.cs | 10 +++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/InternalPlugin.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/InternalPluginData.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/InternalPluginExtensions.cs diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index b302b23e..32d9e981 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -1,6 +1,7 @@ using AIStudio.Dialogs; using AIStudio.Settings; using AIStudio.Settings.DataModel; +using AIStudio.Tools.PluginSystem; using AIStudio.Tools.Rust; using AIStudio.Tools.Services; @@ -81,6 +82,9 @@ protected override async Task OnInitializedAsync() // Ensure that all settings are loaded: await this.SettingsManager.LoadSettings(); + // Ensure that all internal plugins are present: + await PluginFactory.EnsureInternalPlugins(); + // Register this component with the message bus: this.MessageBus.RegisterComponent(this); this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED ]); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/InternalPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/InternalPlugin.cs new file mode 100644 index 00000000..1cbd8ca7 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/InternalPlugin.cs @@ -0,0 +1,7 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum InternalPlugin +{ + LANGUAGE_EN_US, + LANGUAGE_DE_DE, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginData.cs b/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginData.cs new file mode 100644 index 00000000..d7595f2a --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginData.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.PluginSystem; + +public readonly record struct InternalPluginData(PluginType Type, Guid Id, string ShortName) +{ + public string ResourcePath => $"{this.Type.GetDirectory()}/{this.ShortName.ToLowerInvariant()}-{this.Id}"; + + public string ResourceName => $"{this.ShortName.ToLowerInvariant()}-{this.Id}"; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginExtensions.cs new file mode 100644 index 00000000..3a25aa18 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/InternalPluginExtensions.cs @@ -0,0 +1,12 @@ +namespace AIStudio.Tools.PluginSystem; + +public static class InternalPluginExtensions +{ + public static InternalPluginData MetaData(this InternalPlugin plugin) => plugin switch + { + InternalPlugin.LANGUAGE_EN_US => new (PluginType.LANGUAGE, new("97dfb1ba-50c4-4440-8dfa-6575daf543c8"), "en-us"), + InternalPlugin.LANGUAGE_DE_DE => new(PluginType.LANGUAGE, new("43065dbc-78d0-45b7-92be-f14c2926e2dc"), "de-de"), + + _ => new InternalPluginData(PluginType.NONE, Guid.Empty, "unknown") + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index dd14b8fe..9b1d1ebb 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -1,11 +1,90 @@ -using System.Text; +using System.Reflection; + +using AIStudio.Settings; using Lua; +using Microsoft.Extensions.FileProviders; + namespace AIStudio.Tools.PluginSystem; public static class PluginFactory { + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PluginFactory"); + private static readonly string DATA_DIR = SettingsManager.DataDirectory!; + + public static async Task EnsureInternalPlugins() + { + LOG.LogInformation("Start ensuring internal plugins."); + foreach (var plugin in Enum.GetValues()) + { + LOG.LogInformation($"Ensure plugin: {plugin}"); + await EnsurePlugin(plugin); + } + } + + private static async Task EnsurePlugin(InternalPlugin plugin) + { + try + { + #if DEBUG + var basePath = Path.Join(Environment.CurrentDirectory, "Plugins"); + var resourceFileProvider = new PhysicalFileProvider(basePath); + #else + var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Plugins"); + #endif + + var metaData = plugin.MetaData(); + var mainResourcePath = $"{metaData.ResourcePath}/plugin.lua"; + var resourceInfo = resourceFileProvider.GetFileInfo(mainResourcePath); + + if(!resourceInfo.Exists) + { + LOG.LogError($"The plugin {plugin} does not exist. This should not happen, since the plugin is an integral part of AI Studio."); + return; + } + + // Ensure that the additional resources exist: + foreach (var content in resourceFileProvider.GetDirectoryContents(metaData.ResourcePath)) + { + if(content.IsDirectory) + { + LOG.LogError("The plugin contains a directory. This is not allowed."); + continue; + } + + await CopyPluginFile(content, metaData); + } + } + catch + { + LOG.LogError($"Was not able to ensure the plugin: {plugin}"); + } + } + + private static async Task CopyPluginFile(IFileInfo resourceInfo, InternalPluginData metaData) + { + await using var inputStream = resourceInfo.CreateReadStream(); + + var pluginsRoot = Path.Join(DATA_DIR, "plugins"); + var pluginTypeBasePath = Path.Join(pluginsRoot, metaData.Type.GetDirectory()); + + if (!Directory.Exists(pluginsRoot)) + Directory.CreateDirectory(pluginsRoot); + + if (!Directory.Exists(pluginTypeBasePath)) + Directory.CreateDirectory(pluginTypeBasePath); + + var pluginPath = Path.Join(pluginTypeBasePath, metaData.ResourceName); + if (!Directory.Exists(pluginPath)) + Directory.CreateDirectory(pluginPath); + + var pluginFilePath = Path.Join(pluginPath, resourceInfo.Name); + + await using var outputStream = File.Create(pluginFilePath); + await inputStream.CopyToAsync(outputStream); + } + public static async Task LoadAll() { diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs index a26cb03c..b65eb502 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginTypeExtensions.cs @@ -11,4 +11,14 @@ public static class PluginTypeExtensions _ => "Unknown plugin type", }; + + public static string GetDirectory(this PluginType type) => type switch + { + PluginType.LANGUAGE => "languages", + PluginType.ASSISTANT => "assistants", + PluginType.CONFIGURATION => "configurations", + PluginType.THEME => "themes", + + _ => "unknown", + }; } \ No newline at end of file From 51f34392ed4c328beb6122c8d8a044f6e5141aec Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 21 Mar 2025 14:16:54 +0100 Subject: [PATCH 17/31] Added a loader which allows Lua scripts to load other Lua scripts --- .../Tools/PluginSystem/NoPlugin.cs | 2 +- .../Tools/PluginSystem/PluginBase.cs | 5 +- .../Tools/PluginSystem/PluginFactory.cs | 4 +- .../Tools/PluginSystem/PluginLoader.cs | 48 +++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs index ab08241f..a6030603 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs @@ -7,4 +7,4 @@ namespace AIStudio.Tools.PluginSystem; /// /// The Lua state that the plugin was loaded into. /// The error message that occurred while parsing the plugin. -public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(state, PluginType.NONE, parsingError); \ No newline at end of file +public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(string.Empty, state, PluginType.NONE, parsingError); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index 1553f74a..71549b33 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -78,7 +78,7 @@ public abstract class PluginBase /// public bool IsValid => this is not NoPlugin && this.baseIssues.Count == 0 && this.pluginIssues.Count == 0; - protected PluginBase(LuaState state, PluginType type, string parseError = "") + protected PluginBase(string path, LuaState state, PluginType type, string parseError = "") { this.state = state; this.Type = type; @@ -93,6 +93,9 @@ protected PluginBase(LuaState state, PluginType type, string parseError = "") this.state.OpenMathLibrary(); this.state.OpenBitwiseLibrary(); this.state.OpenCoroutineLibrary(); + + // Add the module loader so that the plugin can load other Lua modules: + this.state.ModuleLoader = new PluginLoader(path); var issues = new List(); if(!string.IsNullOrWhiteSpace(parseError)) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 9b1d1ebb..054d4e8d 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -90,7 +90,7 @@ public static async Task LoadAll() } - public static async Task Load(string code, CancellationToken cancellationToken = default) + public static async Task Load(string path, string code, CancellationToken cancellationToken = default) { var state = LuaState.Create(); @@ -114,7 +114,7 @@ public static async Task Load(string code, CancellationToken cancell return type switch { - PluginType.LANGUAGE => new PluginLanguage(state, type), + PluginType.LANGUAGE => new PluginLanguage(path, state, type), _ => new NoPlugin(state, "This plugin type is not supported yet. Please try again with a future version of AI Studio.") }; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs new file mode 100644 index 00000000..ec81f73c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs @@ -0,0 +1,48 @@ +using System.Text; + +using AIStudio.Settings; + +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +/// +/// Loads Lua modules from a plugin directory. +/// +/// +/// Any plugin can load Lua modules from its own directory. This class is used to load these modules. +/// Loading other modules outside the plugin directory is not allowed. +/// +/// The directory where the plugin is located. +public sealed class PluginLoader(string pluginDirectory) : ILuaModuleLoader +{ + private static readonly string PLUGIN_BASE_PATH = Path.Join(SettingsManager.DataDirectory, "plugins"); + + #region Implementation of ILuaModuleLoader + + /// + public bool Exists(string moduleName) + { + // Ensure that the user doesn't try to escape the plugin directory: + if (moduleName.Contains("..") || pluginDirectory.Contains("..")) + return false; + + // Ensure that the plugin directory is nested in the plugin base path: + if (!pluginDirectory.StartsWith(PLUGIN_BASE_PATH, StringComparison.OrdinalIgnoreCase)) + return false; + + var path = Path.Join(pluginDirectory, $"{moduleName}.lua"); + return File.Exists(path); + } + + /// + public async ValueTask LoadAsync(string moduleName, CancellationToken cancellationToken = default) + { + var path = Path.Join(pluginDirectory, $"{moduleName}.lua"); + var code = await File.ReadAllTextAsync(path, Encoding.UTF8, cancellationToken); + + return new(moduleName, code); + } + + #endregion +} \ No newline at end of file From 8fc6cb326bacb54fd74e0d2e0daaf6d4b84de144 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 21 Mar 2025 20:31:59 +0100 Subject: [PATCH 18/31] Simplify NoPlugin --- app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs | 3 +-- .../Tools/PluginSystem/PluginFactory.cs | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs index a6030603..a21ab819 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs @@ -5,6 +5,5 @@ namespace AIStudio.Tools.PluginSystem; /// /// Represents a plugin that could not be loaded. /// -/// The Lua state that the plugin was loaded into. /// The error message that occurred while parsing the plugin. -public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(string.Empty, state, PluginType.NONE, parsingError); \ No newline at end of file +public sealed class NoPlugin(string parsingError) : PluginBase(string.Empty, LuaState.Create(), PluginType.NONE, parsingError); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 054d4e8d..b9658154 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -100,23 +100,23 @@ public static async Task Load(string path, string code, Cancellation } catch (LuaParseException e) { - return new NoPlugin(state, $"Was not able to parse the plugin: {e.Message}"); + return new NoPlugin($"Was not able to parse the plugin: {e.Message}"); } if (!state.Environment["TYPE"].TryRead(out var typeText)) - return new NoPlugin(state, "TYPE does not exist or is not a valid string."); + return new NoPlugin("TYPE does not exist or is not a valid string."); if (!Enum.TryParse(typeText, out var type)) - return new NoPlugin(state, $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); if(type is PluginType.NONE) - return new NoPlugin(state, $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); return type switch { PluginType.LANGUAGE => new PluginLanguage(path, state, type), - _ => new NoPlugin(state, "This plugin type is not supported yet. Please try again with a future version of AI Studio.") + _ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.") }; } } \ No newline at end of file From 24f5127785a595cc00f4aba526874283ea6ebe40 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 21 Mar 2025 20:34:01 +0100 Subject: [PATCH 19/31] Implemented a mechanism for excluding forbidden plugins --- .../Tools/PluginSystem/ForbiddenPlugins.cs | 99 +++++++++++++++++++ .../Tools/PluginSystem/PluginCheckResult.cs | 8 ++ .../Tools/PluginSystem/PluginFactory.cs | 3 + 3 files changed, 110 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs b/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs new file mode 100644 index 00000000..b38459d6 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ForbiddenPlugins.cs @@ -0,0 +1,99 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Checks if a plugin is forbidden. +/// +public static class ForbiddenPlugins +{ + private const string ID_PATTERN = "ID = \""; + private static readonly int ID_PATTERN_LEN = ID_PATTERN.Length; + + /// + /// Checks if the given code represents a forbidden plugin. + /// + /// The code to check. + /// The result of the check. + public static PluginCheckResult Check(ReadOnlySpan code) + { + var endIndex = 0; + var foundAnyId = false; + var id = ReadOnlySpan.Empty; + while (true) + { + // Create a slice of the code starting at the end index. + // This way we can search for all IDs in the code: + code = code[endIndex..]; + + // Read the next ID as a string: + if (!TryGetId(code, out id, out endIndex)) + { + // When no ID was found at all, we block this plugin. + // When another ID was found previously, we allow this plugin. + if(foundAnyId) + return new PluginCheckResult(false, null); + + return new PluginCheckResult(true, "No ID was found."); + } + + // Try to parse the ID as a GUID: + if (!Guid.TryParse(id, out var parsedGuid)) + { + // Again, when no ID was found at all, we block this plugin. + if(foundAnyId) + return new PluginCheckResult(false, null); + + return new PluginCheckResult(true, "The ID is not a valid GUID."); + } + + // Check if the GUID is forbidden: + if (FORBIDDEN_PLUGINS.TryGetValue(parsedGuid, out var reason)) + return new PluginCheckResult(true, reason); + + foundAnyId = true; + } + } + + private static bool TryGetId(ReadOnlySpan code, out ReadOnlySpan id, out int endIndex) + { + // + // Please note: the code variable is a slice of the original code. + // That means the indices are relative to the slice, not the original code. + // + + id = ReadOnlySpan.Empty; + endIndex = 0; + + // Find the next ID: + var idStartIndex = code.IndexOf(ID_PATTERN); + if (idStartIndex < 0) + return false; + + // Find the start index of the value (Guid): + var valueStartIndex = idStartIndex + ID_PATTERN_LEN; + + // Find the end index of the value. In order to do that, + // we create a slice of the code starting at the value + // start index. That means that the end index is relative + // to the inner slice, not the original code nor the outer slice. + var valueEndIndex = code[valueStartIndex..].IndexOf('"'); + if (valueEndIndex < 0) + return false; + + // From the perspective of the start index is the end index + // the length of the value: + endIndex = valueStartIndex + valueEndIndex; + id = code.Slice(valueStartIndex, valueEndIndex); + return true; + } + + /// + /// The forbidden plugins. + /// + /// + /// A dictionary that maps the GUID of a plugin to the reason why it is forbidden. + /// + // ReSharper disable once CollectionNeverUpdated.Local + private static readonly Dictionary FORBIDDEN_PLUGINS = + [ + ]; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs new file mode 100644 index 00000000..f390a47d --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginCheckResult.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents the result of a plugin check. +/// +/// In case the plugin is forbidden, this is true. +/// The message that describes why the plugin is forbidden. +public readonly record struct PluginCheckResult(bool IsForbidden, string? Message); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index b9658154..e20d80f4 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -92,6 +92,9 @@ public static async Task LoadAll() public static async Task Load(string path, string code, CancellationToken cancellationToken = default) { + if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState) + return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}"); + var state = LuaState.Create(); try From ff788b33f26d37682749441fbed54ce054cf32b5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 12:21:00 +0100 Subject: [PATCH 20/31] Add maintenance and deprecation message flags to plugins --- .../plugin.lua | 6 ++++++ .../plugin.lua | 6 ++++++ .../Tools/PluginSystem/PluginBase.cs | 20 +++++++++++++++++++ 3 files changed, 32 insertions(+) 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 10836117..82d00914 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 @@ -28,6 +28,12 @@ CATEGORIES = { "CORE" } -- The target groups for the plugin: TARGET_GROUPS = { "EVERYONE" } +-- The flag for whether the plugin is maintained: +IS_MAINTAINED = true + +-- When the plugin is deprecated, this message will be shown to users: +DEPRECATION_MESSAGE = nil + CONTENT = { Home_LetsGetStarted = "Lass uns anfangen", } \ No newline at end of file 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 ab23db62..99ca6dc4 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 @@ -30,6 +30,12 @@ CATEGORIES = { "CORE" } -- The target groups for the plugin: TARGET_GROUPS = { "EVERYONE" } +-- The flag for whether the plugin is maintained: +IS_MAINTAINED = true + +-- When the plugin is deprecated, this message will be shown to users: +DEPRECATION_MESSAGE = nil + CONTENT = { HOME = CONTENT_HOME, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index 71549b33..d66c73c8 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -63,6 +63,16 @@ public abstract class PluginBase /// The target groups of this plugin. /// public PluginTargetGroup[] TargetGroups { get; } = []; + + /// + /// True, when the plugin is maintained. + /// + public bool IsMaintained { get; } + + /// + /// The message that should be displayed when the plugin is deprecated. + /// + public string? DeprecationMessage { get; } /// /// The issues that occurred during the initialization of this plugin. @@ -146,6 +156,16 @@ protected PluginBase(string path, LuaState state, PluginType type, string parseE else if(this is not NoPlugin) issues.Add(issue); + if(this.TryInitIsMaintained(out issue, out var isMaintained)) + this.IsMaintained = isMaintained; + else if(this is not NoPlugin) + issues.Add(issue); + + if(this.TryInitDeprecationMessage(out issue, out var deprecationMessage)) + this.DeprecationMessage = deprecationMessage; + else if(this is not NoPlugin) + issues.Add(issue); + this.baseIssues = issues; } From 14ce3be083a22beb678a7ab239f1790b5043aaec Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 12:21:56 +0100 Subject: [PATCH 21/31] Add maintenance and deprecation message flags to plugins --- .../Tools/PluginSystem/PluginBase.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index d66c73c8..ff46c824 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -430,6 +430,44 @@ private bool TryInitTargetGroups(out string message, out PluginTargetGroup[] tar message = string.Empty; return true; } + + /// + /// Tries to read the maintenance status of the plugin. + /// + /// The error message, when the maintenance status could not be read. + /// The read maintenance status. + /// True, when the maintenance status could be read successfully. + private bool TryInitIsMaintained(out string message, out bool isMaintained) + { + if (!this.state.Environment["IS_MAINTAINED"].TryRead(out isMaintained)) + { + isMaintained = false; + message = "The field IS_MAINTAINED does not exist or is not a valid boolean."; + return false; + } + + message = string.Empty; + return true; + } + + /// + /// Tries to read the deprecation message of the plugin. + /// + /// The error message, when the deprecation message could not be read. + /// The read deprecation message. + /// True, when the deprecation message could be read successfully. + private bool TryInitDeprecationMessage(out string message, out string? deprecationMessage) + { + if (!this.state.Environment["DEPRECATION_MESSAGE"].TryRead(out deprecationMessage)) + { + deprecationMessage = null; + message = "The field DEPRECATION_MESSAGE does not exist, is not a valid string. This field is optional: use nil to indicate that the plugin is not deprecated."; + return false; + } + + message = string.Empty; + return true; + } #endregion } \ No newline at end of file From dfc8ddb228e484f0417d7cee03c232daeb246951 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:22:33 +0100 Subject: [PATCH 22/31] Updated changelog --- app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md index fe1bf3f2..d5e521f8 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md @@ -1,3 +1,3 @@ # v0.9.33, build 208 (2025-03-xx xx:xx UTC) -- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. +- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. Please note that this feature is still in development; there are no plugins available yet. - Added the Lua library we use for the plugin system to the about page. \ No newline at end of file From 5eafb20e072e47b5840b0bff66137b913538b975 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:23:55 +0100 Subject: [PATCH 23/31] Improved visibility & mutability --- .../Tools/PluginSystem/PluginBase.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index ff46c824..378cf7f3 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -12,7 +12,7 @@ public abstract class PluginBase private readonly IReadOnlyCollection baseIssues; protected readonly LuaState state; - protected List pluginIssues = []; + protected readonly List pluginIssues = []; /// /// The type of this plugin. @@ -177,7 +177,7 @@ protected PluginBase(string path, LuaState state, PluginType type, string parseE /// The error message, when the ID could not be read. /// The read ID. /// True, when the ID could be read successfully. - public bool TryInitId(out string message, out Guid id) + private bool TryInitId(out string message, out Guid id) { if (!this.state.Environment["ID"].TryRead(out var idText)) { @@ -209,7 +209,7 @@ public bool TryInitId(out string message, out Guid id) /// The error message, when the name could not be read. /// The read name. /// True, when the name could be read successfully. - public bool TryInitName(out string message, out string name) + private bool TryInitName(out string message, out string name) { if (!this.state.Environment["NAME"].TryRead(out name)) { @@ -234,7 +234,7 @@ public bool TryInitName(out string message, out string name) /// The error message, when the description could not be read. /// The read description. /// True, when the description could be read successfully. - public bool TryInitDescription(out string message, out string description) + private bool TryInitDescription(out string message, out string description) { if (!this.state.Environment["DESCRIPTION"].TryRead(out description)) { @@ -259,7 +259,7 @@ public bool TryInitDescription(out string message, out string description) /// The error message, when the version could not be read. /// The read version. /// True, when the version could be read successfully. - public bool TryInitVersion(out string message, out PluginVersion version) + private bool TryInitVersion(out string message, out PluginVersion version) { if (!this.state.Environment["VERSION"].TryRead(out var versionText)) { @@ -291,7 +291,7 @@ public bool TryInitVersion(out string message, out PluginVersion version) /// The error message, when the authors could not be read. /// The read authors. /// True, when the authors could be read successfully. - public bool TryInitAuthors(out string message, out string[] authors) + private bool TryInitAuthors(out string message, out string[] authors) { if (!this.state.Environment["AUTHORS"].TryRead(out var authorsTable)) { @@ -322,7 +322,7 @@ public bool TryInitAuthors(out string message, out string[] authors) /// The error message, when the support contact could not be read. /// The read support contact. /// True, when the support contact could be read successfully. - public bool TryInitSupportContact(out string message, out string contact) + private bool TryInitSupportContact(out string message, out string contact) { if (!this.state.Environment["SUPPORT_CONTACT"].TryRead(out contact)) { @@ -347,7 +347,7 @@ public bool TryInitSupportContact(out string message, out string contact) /// The error message, when the source URL could not be read. /// The read source URL. /// True, when the source URL could be read successfully. - public bool TryInitSourceURL(out string message, out string url) + private bool TryInitSourceURL(out string message, out string url) { if (!this.state.Environment["SOURCE_URL"].TryRead(out url)) { @@ -373,7 +373,7 @@ public bool TryInitSourceURL(out string message, out string url) /// The error message, when the categories could not be read. /// The read categories. /// True, when the categories could be read successfully. - public bool TryInitCategories(out string message, out PluginCategory[] categories) + private bool TryInitCategories(out string message, out PluginCategory[] categories) { if (!this.state.Environment["CATEGORIES"].TryRead(out var categoriesTable)) { From 568a4270ae48c1bae474e4c8c357e11a4a8bdcae Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:24:45 +0100 Subject: [PATCH 24/31] Add an init method for UI text content & method to read text tables --- .../Tools/PluginSystem/PluginBase.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index 378cf7f3..4f719d18 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -468,6 +468,54 @@ private bool TryInitDeprecationMessage(out string message, out string? deprecati message = string.Empty; return true; } + + /// + /// Tries to initialize the UI text content of the plugin. + /// + /// The error message, when the UI text content could not be read. + /// The read UI text content. + /// True, when the UI text content could be read successfully. + protected bool TryInitUITextContent(out string message, out Dictionary pluginContent) + { + if (!this.state.Environment["UI_TEXT_CONTENT"].TryRead(out var textTable)) + { + message = "The UI_TEXT_CONTENT table does not exist or is not a valid table."; + pluginContent = []; + return false; + } + + this.ReadTextTable("root", textTable, out pluginContent); + + message = string.Empty; + return true; + } + + /// + /// Reads a flat or hierarchical text table. + /// + /// The parent key(s). + /// The table to read. + /// The read table content. + protected void ReadTextTable(string parent, LuaTable table, out Dictionary tableContent) + { + tableContent = []; + var lastKey = LuaValue.Nil; + while (table.TryGetNext(lastKey, out var pair)) + { + var keyText = pair.Key.ToString(); + if (pair.Value.TryRead(out var value)) + tableContent[$"{parent}::{keyText}"] = value; + + else if (pair.Value.TryRead(out var t)) + { + this.ReadTextTable($"{parent}::{keyText}", t, out var subContent); + foreach (var (k, v) in subContent) + tableContent[k] = v; + } + + lastKey = pair.Key; + } + } #endregion } \ No newline at end of file From bcd0138a96bc1731ded2a46571b53138a725ff35 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:25:14 +0100 Subject: [PATCH 25/31] Implemented the language plugin logic --- .../Tools/PluginSystem/ILanguagePlugin.cs | 21 ++++++++ .../Tools/PluginSystem/PluginLanguage.cs | 48 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs new file mode 100644 index 00000000..a33bf3f5 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs @@ -0,0 +1,21 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a contract for a language plugin. +/// +public interface ILanguagePlugin +{ + /// + /// Tries to get a text from the language plugin. + /// + /// + /// When the key does not exist, the value will be an empty string. + /// Please note that the key is case-sensitive. Furthermore, the keys + /// are in the format "root::key". That means that the keys are + /// hierarchical and separated by "::". + /// + /// The key to use to get the text. + /// The desired text. + /// True if the key exists, false otherwise. + public bool TryGetText(string key, out string value); +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs new file mode 100644 index 00000000..e1676e49 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs @@ -0,0 +1,48 @@ +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +public sealed class PluginLanguage : PluginBase, ILanguagePlugin +{ + private readonly Dictionary content = []; + + private ILanguagePlugin? baseLanguage; + + public PluginLanguage(string path, LuaState state, PluginType type) : base(path, state, type) + { + if (this.TryInitUITextContent(out var issue, out var readContent)) + this.content = readContent; + else + this.pluginIssues.Add(issue); + } + + /// + /// Sets the base language plugin. This plugin will be used to fill in missing keys. + /// + /// The base language plugin to use. + public void SetBaseLanguage(ILanguagePlugin baseLanguagePlugin) => this.baseLanguage = baseLanguagePlugin; + + /// + /// Tries to get a text from the language plugin. + /// + /// + /// When the key neither in the base language nor in this language exist, + /// the value will be an empty string. Please note that the key is case-sensitive. + /// Furthermore, the keys are in the format "root::key". That means that + /// the keys are hierarchical and separated by "::". + /// + /// The key to use to get the text. + /// The desired text. + /// True if the key exists, false otherwise. + public bool TryGetText(string key, out string value) + { + if (this.content.TryGetValue(key, out value!)) + return true; + + if(this.baseLanguage is not null && this.baseLanguage.TryGetText(key, out value)) + return true; + + value = string.Empty; + return false; + } +} \ No newline at end of file From 2d7419cfcce380f2312085b88be2c0b7a2c30abc Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:27:25 +0100 Subject: [PATCH 26/31] Updated language plugins for DE-DE and EN-US --- .../contentHome.lua | 3 +++ .../de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua | 4 ++-- .../en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/contentHome.lua diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/contentHome.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/contentHome.lua new file mode 100644 index 00000000..f26d9aff --- /dev/null +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/contentHome.lua @@ -0,0 +1,3 @@ +CONTENT_HOME = { + LetsGetStarted = "Lass uns anfangen", +} \ 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 82d00914..4801b782 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 @@ -34,6 +34,6 @@ IS_MAINTAINED = true -- When the plugin is deprecated, this message will be shown to users: DEPRECATION_MESSAGE = nil -CONTENT = { - Home_LetsGetStarted = "Lass uns anfangen", +UI_TEXT_CONTENT = { + HOME = CONTENT_HOME, } \ No newline at end of file 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 99ca6dc4..6e2fea50 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 @@ -36,6 +36,6 @@ IS_MAINTAINED = true -- When the plugin is deprecated, this message will be shown to users: DEPRECATION_MESSAGE = nil -CONTENT = { +UI_TEXT_CONTENT = { HOME = CONTENT_HOME, } \ No newline at end of file From 7ebdeaad9ff879216602e41c0b186a126171434b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 14:29:43 +0100 Subject: [PATCH 27/31] Delete app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md --- app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md deleted file mode 100644 index d5e521f8..00000000 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md +++ /dev/null @@ -1,3 +0,0 @@ -# v0.9.33, build 208 (2025-03-xx xx:xx UTC) -- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. Please note that this feature is still in development; there are no plugins available yet. -- Added the Lua library we use for the plugin system to the about page. \ No newline at end of file From 4c4ee62d8e04764b931d709f94ca6d2418c50412 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 21:04:22 +0100 Subject: [PATCH 28/31] Merged project file & changelog from main --- .../MindWork AI Studio.csproj | 16 +- app/MindWork AI Studio/packages.lock.json | 180 ++++++++---------- .../wwwroot/changelog/v0.9.33.md | 2 + 3 files changed, 94 insertions(+), 104 deletions(-) create mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 767453e3..bdfe8287 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -5,7 +5,7 @@ Thorsten Sommer - net8.0 + net9.0 enable CS8600;CS8602;CS8603 enable @@ -31,6 +31,7 @@ CS8974: Converting method group to non-delegate type; Did you intend to invoke the method? We have this issue with MudBlazor validation methods. --> IL2026, CS8974 + latest @@ -40,17 +41,16 @@ + - - - - - - - + + + + + diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index 4a1ca441..37a21d8a 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -1,67 +1,61 @@ { "version": 1, "dependencies": { - "net8.0": { + "net9.0": { "CodeBeam.MudBlazor.Extensions": { "type": "Direct", - "requested": "[7.1.0, )", - "resolved": "7.1.0", - "contentHash": "qbyCT4XMc/lbi2XdkUh9aSPu97RUPDisU7fpTCU0Q4nGywqJsAxrwcpaxJqoycq+uj3smwX5zOn6yzfsHUObeQ==", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "MNxReFDmME1OLhHsD7lrfQZ9cMu5X60PIvidyFueq0ddDKHlagHS6CuYBZRQ62rHqmocdHWOLyJ73t2Im0NnWw==", "dependencies": { "BuildBundlerMinifier": "3.2.449", - "CsvHelper": "31.0.3", - "Microsoft.AspNetCore.Components": "8.0.11", - "Microsoft.AspNetCore.Components.Web": "8.0.11", - "MudBlazor": "7.15.0", + "CsvHelper": "33.0.1", + "Microsoft.AspNetCore.Components": "9.0.1", + "Microsoft.AspNetCore.Components.Web": "9.0.1", + "MudBlazor": "8.0.0", "ZXing.Net": "0.16.9" } }, "HtmlAgilityPack": { "type": "Direct", - "requested": "[1.11.74, )", - "resolved": "1.11.74", - "contentHash": "q0wRGbegtr4sZXjCNoV3OeRLTOcTNJQKiO9etNVSKPoTo33unmSK8Ahg36C4jIg/Hd3aw8YnTQjtKpBy+wlOpg==" - }, - "LuaCSharp": { - "type": "Direct", - "requested": "[0.4.2, )", - "resolved": "0.4.2", - "contentHash": "wS0hp7EFx+llJ/U/7Ykz4FSmQf8DH4mNejwo5/h1KuFyguzGZbKhTO22X54pXnuqa5cIKfEfQ29dluHHnCX05Q==" + "requested": "[1.12.0, )", + "resolved": "1.12.0", + "contentHash": "VHtVZmfoYhQyA/POvZRLuTpCz1zhzIDrdYRJIRV73e9wKAzjW71biYNOHOWx8MxEX3TE4TWVfx1QDRoZcj2AWw==" }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[9.0.2, )", - "resolved": "9.0.2", - "contentHash": "guQjtnav6bkds3jBqvzLUN3CtFhrwwE2MvES4kukalrFnRkFCncumYBrycM3urilJ7ffetA1/XBcD5ChMJa+AA==", + "requested": "[9.0.3, )", + "resolved": "9.0.3", + "contentHash": "UKfKGlZ7jKfe6v4rLsjnH/mGbD3e4YD9EK+Uobu+KIxwfhZuLLCtXm4CWTOf2s1t+ItmMs0QqbSJAXaMXCxLOw==", "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.2" + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.3" } }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.13, )", - "resolved": "8.0.13", - "contentHash": "R19ZTaRiQAK+xo9ZwaHbF/1vb1wwR1Wn5+sqp9v8+CDjbdS8R6qftKdw0VSXWKm7VAMi7P+NCU4zxDzhEWcAwQ==" + "requested": "[9.0.3, )", + "resolved": "9.0.3", + "contentHash": "1rqGTfubVg0qj2PsK6esyq3PIxtYJYrN3LsYUV9RrvH3anmt3fT3ozYdAZZH4U8JU/pt5pPIUk8NBSu26wtekA==" }, "MudBlazor": { "type": "Direct", - "requested": "[7.16.0, )", - "resolved": "7.16.0", - "contentHash": "68bEFn7MLCyOB4yYxtvZG+AA+bZbxBDcIOhnSVFrHrAg8LLXVw1LxHCp9EyRktlREpnoAO1JxINp+5WeHc9Z4w==", + "requested": "[8.4.0, )", + "resolved": "8.4.0", + "contentHash": "JdpJXe13fhPElc4ajoDEr7tbzib+N1SVUaov21lZAVd0DgRWuVTJsfVyrEFCD/oSopRcvub5cpHSJM7MYUVCfQ==", "dependencies": { - "Microsoft.AspNetCore.Components": "8.0.8", - "Microsoft.AspNetCore.Components.Web": "8.0.8", - "Microsoft.Extensions.Localization": "8.0.8" + "Microsoft.AspNetCore.Components": "9.0.1", + "Microsoft.AspNetCore.Components.Web": "9.0.1", + "Microsoft.Extensions.Localization": "9.0.1" } }, "MudBlazor.Markdown": { "type": "Direct", - "requested": "[7.14.0, )", - "resolved": "7.14.0", - "contentHash": "c6kFfMgUxKxYK1AkGHRNg5B9jw+iwxelBuDpaI5N+guOleT37BxYetYlD0iOew/n1plhobynUJn3Kk4MXhv6RA==", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "0DcXQFEIgKJsaMCDva0Ck3gempoctyc7s8GLK5VagozlZdXql6W4SKX/imM/NfyfV7SxLrUTRJyLJX0Te+02sQ==", "dependencies": { - "Markdig": "0.37.0", - "MudBlazor": "7.14.0" + "Markdig": "0.40.0", + "MudBlazor": "8.0.0" } }, "ReverseMarkdown": { @@ -80,132 +74,126 @@ }, "CsvHelper": { "type": "Transitive", - "resolved": "31.0.3", - "contentHash": "ygck8DR4mG/VDA/LgIVVGpEtXXPDVaaNZNJGrOAJ4pckVw4MbAQ3n/u6YFDv3bwlQhlxTmPhCyk5E4hxe96Crg==" + "resolved": "33.0.1", + "contentHash": "fev4lynklAU2A9GVMLtwarkwaanjSYB4wUqO2nOJX5hnzObORzUqVLe+bDYCUyIIRQM4o5Bsq3CcyJR89iMmEQ==" }, "Markdig": { "type": "Transitive", - "resolved": "0.37.0", - "contentHash": "biiu4MTPFjW55qw6v5Aphtj0MjDLJ14x8ndZwkJUHIeqvaSGKeqhLY7S7Vu/S3k7/c9KwhhnaCDP9hdFNUhcNA==" + "resolved": "0.40.0", + "contentHash": "4ve14zs+gt1irldTQE3y5FLAHuzmhW7T99lAAvVipe/q2LWT/nUCO0iICb9TXGvMX6n7Z1OZroFXkdSy91rO8w==" }, "Microsoft.AspNetCore.Authorization": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "ACaLyjBSz9WUzbaJe0Sv09/FihRNHYlRUIj3uQ8CZFFByf6Qwv3+PXnbltidFKz2iOyqdvppQias3emdQUY2nA==", + "resolved": "9.0.1", + "contentHash": "WgLlLBlMczb2+QLNG6sM95OUZ0EBztz60k/N75tjIgpyu0SdpIfYytAmX/7JJAjRTZF0c/CrWaQV+SH9FuGsrA==", "dependencies": { - "Microsoft.AspNetCore.Metadata": "8.0.11", - "Microsoft.Extensions.Logging.Abstractions": "8.0.2", - "Microsoft.Extensions.Options": "8.0.2" + "Microsoft.AspNetCore.Metadata": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1" } }, "Microsoft.AspNetCore.Components": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "kyhSQcVEQvMnv2BNRn7JRgYCr+PIO5Uh1mhIFdCNycxE/k8NsI72sV693s1KVmVebMA8g3hTBmfBEheWb3hhww==", + "resolved": "9.0.1", + "contentHash": "6pwfbQKNtvPkbF4tCGiAKGyt6BVpu58xAXz7u2YXcUKTNmNxrymbG1mEyMc0EPzVdnquDDqTyfXM3mC1EJycxQ==", "dependencies": { - "Microsoft.AspNetCore.Authorization": "8.0.11", - "Microsoft.AspNetCore.Components.Analyzers": "8.0.11" + "Microsoft.AspNetCore.Authorization": "9.0.1", + "Microsoft.AspNetCore.Components.Analyzers": "9.0.1" } }, "Microsoft.AspNetCore.Components.Analyzers": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "4JtFt5IR0ixuFpwY6D2Xi5R+vZQ6iykd2EuG3puHETCOZOgYG8M538LCY1lbgQTkHOL04YKDjQTQu8PU/BaXRQ==" + "resolved": "9.0.1", + "contentHash": "I8Rs4LXT5UQxM5Nin2+Oj8aSY2heszSZ3EyTLgt3mxmfiRPrVO7D8NNSsf1voI2Gb0qFJceof/J5c9E+nfNuHw==" }, "Microsoft.AspNetCore.Components.Forms": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "60g+idqaiVhPVNOqauy/vH5lREpjcuKl3/w6zJhdU1PFWg4jtdoyIPQH+qxBKsUohkELhH3cRfzGRKElVuZuwg==", + "resolved": "9.0.1", + "contentHash": "KyULVU32bLz74LWDwPEwNUEllTehzWJuM7YAsz80rMKEzvR0K8cRjRzO0fnN/nfydMeLRRlbI0xj8wnEAymLVw==", "dependencies": { - "Microsoft.AspNetCore.Components": "8.0.11" + "Microsoft.AspNetCore.Components": "9.0.1" } }, "Microsoft.AspNetCore.Components.Web": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "IDmjQ/K7hv6zUEz2LsCkQBngZx6PMnty8OdSPf0hYGMpC+4Yi37pgCc/25fFu3CSBe8nDirqTrqKtfToHWCpbw==", + "resolved": "9.0.1", + "contentHash": "LI0vjYEd9MaDZPDQxPCn4gGYDkEC5U9rp1nWZo7rPozJxgTG2zU3WERujxTi2LeAC2ZzdXlOVCrUyPQ55LZV2A==", "dependencies": { - "Microsoft.AspNetCore.Components": "8.0.11", - "Microsoft.AspNetCore.Components.Forms": "8.0.11", - "Microsoft.Extensions.DependencyInjection": "8.0.1", - "Microsoft.Extensions.Primitives": "8.0.0", - "Microsoft.JSInterop": "8.0.11", - "System.IO.Pipelines": "8.0.0" + "Microsoft.AspNetCore.Components": "9.0.1", + "Microsoft.AspNetCore.Components.Forms": "9.0.1", + "Microsoft.Extensions.DependencyInjection": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1", + "Microsoft.JSInterop": "9.0.1" } }, "Microsoft.AspNetCore.Metadata": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "cy04xnMSTXTkRPjEwseRz57R5zjR/CWsdEOHH6NhWbNl97k+U1w6dSjqIOC7kv08tyzmM30FzIilSDtE5HdL/A==" + "resolved": "9.0.1", + "contentHash": "EZnHifamF7IFEIyjAKMtJM3I/94OIe72i3P09v5oL0twmsmfQwal6Ni3m8lbB5mge3jWFhMozeW+rUdRSqnXRQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", + "resolved": "9.0.1", + "contentHash": "qZI42ASAe3hr2zMSA6UjM92pO1LeDq5DcwkgSowXXPY8I56M76pEKrnmsKKbxagAf39AJxkH2DY4sb72ixyOrg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + "resolved": "9.0.1", + "contentHash": "Tr74eP0oQ3AyC24ch17N8PuEkrPbD0JqIfENCYqmgKYNOmL8wQKzLJu3ObxTUDrjnn4rHoR1qKa37/eQyHmCDA==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "IcOBmTlr2jySswU+3x8c3ql87FRwTVPQgVKaV5AXzPT5u0VItfNU8SMbESpdSp5STwxT/1R99WYszgHWsVkzhg==", + "resolved": "9.0.3", + "contentHash": "umczZ3+QPpzlrW/lkvy+IB0p52+qZ5w++aqx2lTCMOaPKzwcbVdrJgiQ3ajw5QWBp7gChLUiCYkSlWUpfjv24g==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.2" + "Microsoft.Extensions.Primitives": "9.0.3" } }, "Microsoft.Extensions.Localization": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "n32ZGmc2UOv9R/xsBHQPOxBsqCSAAJSoMtFqoQ8zOegB2P+H6+773OyCWg4jRBkO/3dmCyBJXB0yLAOVW2/C/w==", + "resolved": "9.0.1", + "contentHash": "UgvX4Yb2T3tEsKT30ktZr0H7kTRPapCgEH0bdTwxiEGSdA39/hAQMvvb+vgHpqmevDU5+puyI9ujRkmmbF946w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1", - "Microsoft.Extensions.Localization.Abstractions": "8.0.8", - "Microsoft.Extensions.Logging.Abstractions": "8.0.1", - "Microsoft.Extensions.Options": "8.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Localization.Abstractions": "9.0.1", + "Microsoft.Extensions.Logging.Abstractions": "9.0.1", + "Microsoft.Extensions.Options": "9.0.1" } }, "Microsoft.Extensions.Localization.Abstractions": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "WYIsqP/A6dH/LWJznrvgoNPc7Q+CUJD0E78765GL3aonBtyFK1BKtPzBvvlnrr3SVqSO2r6xJCLgCEiCMG1gfA==" + "resolved": "9.0.1", + "contentHash": "CABog43lyaZQMjmlktuImCy6zmAzRBaXqN81uPaMQjlp//ISDVYItZPh6KWpWRF4MY/B67X5oDc3JTUpfdocZw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", + "resolved": "9.0.1", + "contentHash": "w2gUqXN/jNIuvqYwX3lbXagsizVNXYyt6LlF57+tMve4JYCEgCMMAjRce6uKcDASJgpMbErRT1PfHy2OhbkqEA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "resolved": "9.0.1", + "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", + "Microsoft.Extensions.Primitives": "9.0.1" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "puBMtKe/wLuYa7H6docBkLlfec+h8L35DXqsDKKJgW0WY5oCwJ3cBJKcDaZchv6knAyqOMfsl6VUbaR++E5LXA==" + "resolved": "9.0.3", + "contentHash": "yCCJHvBcRyqapMSNzP+kTc57Eaavq2cr5Tmuil6/XVnipQf5xmskxakSQ1enU6S4+fNg3sJ27WcInV64q24JsA==" }, "Microsoft.JSInterop": { "type": "Transitive", - "resolved": "8.0.11", - "contentHash": "UYSbAkNGTWVUne3I04/9IRQel3Bt1Ww6Y5cjvZEZ89rWhBD1yWu7YDotvQS62V6mgSfFaXXPGrCUm1VG824QXw==" - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "FHNOatmUq0sqJOkTx+UF/9YK1f180cnW5FVqnQMvYUN0elp6wFzbtPSiqbo1/ru8ICp43JM1i7kKkk6GsNGHlA==" + "resolved": "9.0.1", + "contentHash": "/xBwIfb0YoC2Muv6EsHjxpqZw2aKv94+i0g0FWZvqvGv3DeAy+8wipAuECVvKYEs2EIclRD41bjajHLoD6mTtw==" }, "ZXing.Net": { "type": "Transitive", @@ -213,6 +201,6 @@ "contentHash": "7WaVMHklpT3Ye2ragqRIwlFRsb6kOk63BOGADV0fan3ulVfGLUYkDi5yNUsZS/7FVNkWbtHAlDLmu4WnHGfqvQ==" } }, - "net8.0/osx-arm64": {} + "net9.0/osx-arm64": {} } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md new file mode 100644 index 00000000..49725cbe --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.33.md @@ -0,0 +1,2 @@ +# v0.9.33, build 208 (2025-03-11 08:14 UTC) +- Fixed a bug where the authentication methods for added ERI servers were not applied correctly. Thanks Kerstin `KeSah001` for reporting this issue. \ No newline at end of file From 48f2d715bded448e9de57098c2a69307280348b5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 21:08:00 +0100 Subject: [PATCH 29/31] Added changelog --- app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md new file mode 100644 index 00000000..4fb08d00 --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md @@ -0,0 +1,3 @@ +# v0.9.39, build 214 (2025-03-xx xx:xx UTC) +- Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. Please note that this feature is still in development; there are no plugins available yet. +- Added the Lua library we use for the plugin system to the about page. \ No newline at end of file From 262cebd498060448517f4b10641179b5d4e3b06c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 21:08:23 +0100 Subject: [PATCH 30/31] Embed all internal plugins --- app/MindWork AI Studio/MindWork AI Studio.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index bdfe8287..b03fd77c 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -41,8 +41,8 @@ - + From d0e94c9eb6b0e52956523555d433594aff8b360b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 22 Mar 2025 21:08:35 +0100 Subject: [PATCH 31/31] Added the Lua library --- app/MindWork AI Studio/MindWork AI Studio.csproj | 13 +++++++------ app/MindWork AI Studio/packages.lock.json | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index b03fd77c..67374863 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -46,12 +46,13 @@ - - - - - - + + + + + + + diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index 37a21d8a..8020366c 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -22,6 +22,12 @@ "resolved": "1.12.0", "contentHash": "VHtVZmfoYhQyA/POvZRLuTpCz1zhzIDrdYRJIRV73e9wKAzjW71biYNOHOWx8MxEX3TE4TWVfx1QDRoZcj2AWw==" }, + "LuaCSharp": { + "type": "Direct", + "requested": "[0.4.2, )", + "resolved": "0.4.2", + "contentHash": "wS0hp7EFx+llJ/U/7Ykz4FSmQf8DH4mNejwo5/h1KuFyguzGZbKhTO22X54pXnuqa5cIKfEfQ29dluHHnCX05Q==" + }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", "requested": "[9.0.3, )",