diff --git a/AGENTS.md b/AGENTS.md index e0859dae..d559c62e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,12 +112,16 @@ Plugins can configure: - Chat templates - etc. -When adding configuration options, update: -- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs`: In method `TryProcessConfiguration` register new options. -- `app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs`: In method `LoadAll` check for leftover configuration. -- The corresponding data class in `app/MindWork AI Studio/Settings/DataModel/` to call `ManagedConfiguration.Register(...)`, when adding config options (in contrast to complex config. objects) -- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs` for parsing logic of complex configuration objects. -- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` to document the new configuration option. +Configuration plugins provide three kinds of values: +- **Managed settings:** simple values such as booleans, numbers, strings, enums, lists, or sets handled through `ManagedConfiguration`. These values may be locked or used as organization defaults. +- **Managed configuration objects:** complex Lua tables that are persisted into `SettingsManager.ConfigurationData`, implement `IConfigurationObject`, and are cleaned up through `PluginConfigurationObject.CleanLeftOverConfigurationObjects(...)`. Examples include providers, profiles, chat templates, data sources, and document analysis policies. +- **Live plugin content:** complex Lua tables that implement `ILivePluginContent` and are read live from running plugins instead of being persisted to `ConfigurationData`. Examples include `MANDATORY_INFOS` and `INTRODUCTIONS`. If live plugin content creates persistent side data, add a dedicated cleanup path for that side data, like mandatory-info acceptances. + +When adding configuration plugin capabilities: +- For managed settings, update the corresponding data class in `app/MindWork AI Studio/Settings/DataModel/` to call `ManagedConfiguration.Register(...)`, process the setting in `PluginConfiguration.TryProcessConfiguration`, and check for leftover managed configuration in `PluginFactory.Loading.LoadAll`. +- For managed configuration objects, update `PluginConfigurationObject.cs` and `PluginConfigurationObjectType.cs`, persist them in the appropriate `ConfigurationData` collection, and add cleanup via `PluginConfigurationObject.CleanLeftOverConfigurationObjects(...)`. +- For live plugin content, add a data type implementing `ILivePluginContent`, parse it in `PluginConfiguration`, expose it through `PluginFactory`, and add any required cleanup only for persistent side data. +- Always document the new capability in `app/MindWork AI Studio/Plugins/configuration/plugin.lua`. ## RAG (Retrieval-Augmented Generation) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 232abd1e..b1cb1207 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -6067,6 +6067,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T144565305"] = "The app requires minimal -- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit." +-- Version +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1573770551"] = "Version" + -- Assistants UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants" diff --git a/app/MindWork AI Studio/Pages/Home.razor b/app/MindWork AI Studio/Pages/Home.razor index abf7ffb7..d6c4158a 100644 --- a/app/MindWork AI Studio/Pages/Home.razor +++ b/app/MindWork AI Studio/Pages/Home.razor @@ -8,39 +8,52 @@ - + - - - @T("Welcome to MindWork AI Studio!") - - - @T("Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider.") - - - @T("Here's what makes MindWork AI Studio stand out:") - - - - @T("We hope you enjoy using MindWork AI Studio to bring your AI projects to life!") - - + @if (this.SettingsManager.ConfigurationData.App.ShowIntroduction) + { + + + @T("Welcome to MindWork AI Studio!") + + + @T("Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider.") + + + @T("Here's what makes MindWork AI Studio stand out:") + + + + @T("We hope you enjoy using MindWork AI Studio to bring your AI projects to life!") + + + } - + @foreach (var introduction in this.introductions) + { + + + @T("Version"): @introduction.VersionText + + + + } + + - + @if (this.SettingsManager.ConfigurationData.App.ShowQuickStartGuide) { - + } - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index b44724d0..5fb95872 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -1,5 +1,6 @@ using AIStudio.Components; using AIStudio.Settings.DataModel; +using AIStudio.Tools.PluginSystem; using Microsoft.AspNetCore.Components; @@ -18,13 +19,25 @@ public partial class Home : MSGComponentBase private string LastChangeContent { get; set; } = string.Empty; private TextItem[] itemsAdvantages = []; + + private List introductions = []; + private string expandedPanelId = string.Empty; + private int expansionPanelsRenderKey; + + private const string PANEL_ID_BUILT_IN_INTRODUCTION = "built-in-introduction"; + private const string PANEL_ID_LAST_CHANGELOG = "last-changelog"; + private const string PANEL_ID_VISION = "vision"; + private const string PANEL_ID_QUICK_START_GUIDE = "quick-start-guide"; #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); await base.OnInitializedAsync(); this.InitializeAdvantagesItems(); + this.RefreshIntroductionPanels(); + this.EnsureDefaultExpandedPanel(); // Read the last change content asynchronously // without blocking the UI thread: @@ -69,10 +82,14 @@ public partial class Home : MSGComponentBase { case Event.PLUGINS_RELOADED: this.InitializeAdvantagesItems(); + this.RefreshIntroductionPanels(); + this.EnsureDefaultExpandedPanel(); await this.InvokeAsync(this.StateHasChanged); break; case Event.CONFIGURATION_CHANGED: + this.RefreshIntroductionPanels(); + this.EnsureDefaultExpandedPanel(); await this.InvokeAsync(this.StateHasChanged); break; } @@ -80,6 +97,42 @@ public partial class Home : MSGComponentBase #endregion + private void RefreshIntroductionPanels() + { + this.introductions = PluginFactory.GetIntroductions().ToList(); + } + + private string GetDefaultExpandedPanelId() + { + if (this.SettingsManager.ConfigurationData.App.ShowIntroduction) + return PANEL_ID_BUILT_IN_INTRODUCTION; + + var firstIntroduction = this.introductions.FirstOrDefault(); + return firstIntroduction is not null + ? IntroductionPanelId(firstIntroduction) + : PANEL_ID_LAST_CHANGELOG; + } + + private void EnsureDefaultExpandedPanel() + { + this.expandedPanelId = this.GetDefaultExpandedPanelId(); + this.expansionPanelsRenderKey++; + } + + private bool IsPanelExpanded(string panelId) => string.Equals(this.expandedPanelId, panelId, StringComparison.Ordinal); + + private Task SetPanelExpanded(string panelId, bool isExpanded) + { + if (isExpanded) + this.expandedPanelId = panelId; + else if (this.IsPanelExpanded(panelId)) + this.expandedPanelId = string.Empty; + + return Task.CompletedTask; + } + + private static string IntroductionPanelId(DataIntroduction introduction) => $"introduction:{introduction.Id}"; + private async Task ReadLastChangeAsync() { var latest = Changelog.LOGS.MaxBy(n => n.Build); diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 070952ce..e2953424 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -207,6 +207,9 @@ CONFIG["SETTINGS"] = {} -- Configure whether the quick start guide is shown on the welcome page. -- CONFIG["SETTINGS"]["DataApp.ShowQuickStartGuide"] = false +-- Configure whether the built-in introduction is shown on the welcome page. +-- CONFIG["SETTINGS"]["DataApp.ShowIntroduction"] = false + -- Configure the user permission to add providers: -- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false @@ -336,6 +339,26 @@ CONFIG["CHAT_TEMPLATES"] = {} -- } -- } +-- Introduction texts shown as expansion panels on the welcome page: +CONFIG["INTRODUCTIONS"] = {} + +-- An example introduction: +-- CONFIG["INTRODUCTIONS"][#CONFIG["INTRODUCTIONS"]+1] = { +-- ["Id"] = "00000000-0000-0000-0000-000000000000", +-- ["Title"] = "Welcome to Your Organization's AI Studio", +-- ["Version"] = "1", +-- ["Index"] = 1, +-- ["Markdown"] = [===[ +-- ## Getting Started +-- +-- This AI Studio installation is managed by your organization. +-- Please use the preconfigured providers and follow your internal +-- AI usage guidelines. +-- +-- Further information is available in the [internal wiki](https://example.org/wiki). +-- ]===] +-- } + -- Mandatory infos that users must explicitly accept before using AI Studio: -- AI Studio asks users again when Version, Title, or Markdown change. -- Changing Version additionally allows the UI to communicate that a new version is available. 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 bd075701..849ce7ab 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 @@ -6069,6 +6069,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T144565305"] = "Die App benötigt nur we -- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "Sie zahlen nur für das, was Sie tatsächlich nutzen – das kann günstiger sein als monatliche Abos wie ChatGPT Plus, vor allem bei gelegentlicher Nutzung. Aber Vorsicht: Bei sehr intensiver Nutzung können die API-Kosten deutlich höher ausfallen. Leider bieten die Anbieter derzeit keine Möglichkeit, die aktuellen Kosten direkt in der App anzuzeigen. Prüfen Sie deshalb regelmäßig Ihr Konto beim jeweiligen Anbieter, um ihre Ausgaben im Blick zu behalten. Nutzen Sie, wenn möglich, Prepaid-Optionen und legen Sie ein Ausgabenlimit fest." +-- Version +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1573770551"] = "Version" + -- Assistants UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistenten" 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 df545001..c7c66936 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 @@ -6069,6 +6069,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T144565305"] = "The app requires minimal -- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit." +-- Version +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1573770551"] = "Version" + -- Assistants UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants" diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index c9352514..a0c2c58e 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -57,6 +57,11 @@ public sealed class DataApp(Expression>? configSelection = n /// public StartPage StartPage { get; set; } = ManagedConfiguration.Register(configSelection, n => n.StartPage, StartPage.HOME); + /// + /// Should the built-in introduction be visible on the home page? + /// + public bool ShowIntroduction { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowIntroduction, true); + /// /// Should the quick start guide be visible on the home page? /// diff --git a/app/MindWork AI Studio/Settings/DataModel/DataIntroduction.cs b/app/MindWork AI Studio/Settings/DataModel/DataIntroduction.cs new file mode 100644 index 00000000..22a6d4ab --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/DataIntroduction.cs @@ -0,0 +1,87 @@ +using AIStudio.Tools.PluginSystem; + +using Lua; + +namespace AIStudio.Settings.DataModel; + +public sealed record DataIntroduction : ILivePluginContent +{ + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); + + /// + /// The stable ID of the introduction. + /// + public string Id { get; private init; } = string.Empty; + + /// + /// The ID of the enterprise configuration plugin that provides this introduction. + /// + public Guid EnterpriseConfigurationPluginId { get; private init; } = Guid.Empty; + + /// + /// The title shown to the user. + /// + public string Title { get; private init; } = string.Empty; + + /// + /// The configured version string shown to the user. + /// + public string VersionText { get; private init; } = string.Empty; + + /// + /// The sort index used on the home page. + /// + public int Index { get; private init; } = 1; + + /// + /// The Markdown content shown to the user. + /// + public string Markdown { get; private init; } = string.Empty; + + public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, out DataIntroduction introduction) + { + introduction = new DataIntroduction(); + if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead(out var idText) || !Guid.TryParse(idText, out var id)) + { + LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid ID. The ID must be a valid GUID.", idx); + return false; + } + + if (!table.TryGetValue("Title", out var titleValue) || !titleValue.TryRead(out var title) || string.IsNullOrWhiteSpace(title)) + { + LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Title field.", idx); + return false; + } + + if (!table.TryGetValue("Version", out var versionValue) || !versionValue.TryRead(out var versionText) || string.IsNullOrWhiteSpace(versionText)) + { + LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Version field.", idx); + return false; + } + + if (!table.TryGetValue("Markdown", out var markdownValue) || !markdownValue.TryRead(out var markdown) || string.IsNullOrWhiteSpace(markdown)) + { + LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Markdown field.", idx); + return false; + } + + var index = 1; + if (table.TryGetValue("Index", out var indexValue) && !indexValue.TryRead(out index)) + { + LOG.LogWarning("The configured introduction {IntroductionIndex} does not contain a valid Index field. The Index must be an integer.", idx); + return false; + } + + introduction = new DataIntroduction + { + Id = id.ToString(), + Title = title, + VersionText = versionText, + Index = index, + Markdown = AIStudio.Tools.Markdown.RemoveSharedIndentation(markdown), + EnterpriseConfigurationPluginId = configPluginId, + }; + + return true; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs index 638ba6d8..d588332f 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataMandatoryInfo.cs @@ -1,11 +1,13 @@ using System.Security.Cryptography; using System.Text; +using AIStudio.Tools.PluginSystem; + using Lua; namespace AIStudio.Settings.DataModel; -public sealed record DataMandatoryInfo +public sealed record DataMandatoryInfo : ILivePluginContent { private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILivePluginContent.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILivePluginContent.cs new file mode 100644 index 00000000..4ad5a261 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILivePluginContent.cs @@ -0,0 +1,18 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents complex content from a configuration plugin that is read live from +/// running plugins and is not persisted to the settings data model. +/// +public interface ILivePluginContent +{ + /// + /// The stable ID of the live plugin content. + /// + public string Id { get; } + + /// + /// The ID of the enterprise configuration plugin that provides this content. + /// + public Guid EnterpriseConfigurationPluginId { get; } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 8bd55c93..e2735273 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -14,6 +14,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT private List configObjects = []; private List mandatoryInfos = []; + private List introductions = []; /// /// The list of configuration objects. Configuration objects are, e.g., providers or chat templates. @@ -22,9 +23,16 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT /// /// The list of mandatory infos provided by this configuration plugin. + /// Mandatory infos are live plugin content and are not persisted to ConfigurationData. /// public IReadOnlyList MandatoryInfos => this.mandatoryInfos; + /// + /// The list of introductions provided by this configuration plugin. + /// Introductions are live plugin content and are not persisted to ConfigurationData. + /// + public IReadOnlyList Introductions => this.introductions; + /// /// True/false when explicitly configured in the plugin, otherwise null. /// @@ -130,6 +138,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT { this.configObjects.Clear(); this.mandatoryInfos.Clear(); + this.introductions.Clear(); // Ensure that the main CONFIG table exists and is a valid Lua table: if (!this.State.Environment["CONFIG"].TryRead(out var mainTable)) @@ -154,6 +163,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: what should be the start page? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.StartPage, this.Id, settingsTable, dryRun); + // Config: show built-in introduction on the home page? + ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowIntroduction, this.Id, settingsTable, dryRun); + // Config: show quick start guide on the home page? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.ShowQuickStartGuide, this.Id, settingsTable, dryRun); @@ -206,6 +218,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Handle configured mandatory infos: this.TryReadMandatoryInfos(mainTable); + + // Handle configured introductions: + this.TryReadIntroductions(mainTable); // Config: preselected provider? ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun); @@ -240,4 +255,25 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT LOG.LogWarning("The table 'MANDATORY_INFOS' entry at index {Index} does not contain a valid mandatory info (config plugin id: {ConfigPluginId}).", i, this.Id); } } + + private void TryReadIntroductions(LuaTable mainTable) + { + if (!mainTable.TryGetValue("INTRODUCTIONS", out var introductionsValue) || !introductionsValue.TryRead(out var introductionsTable)) + return; + + for (var i = 1; i <= introductionsTable.ArrayLength; i++) + { + var luaIntroductionValue = introductionsTable[i]; + if (!luaIntroductionValue.TryRead(out var luaIntroductionTable)) + { + LOG.LogWarning("The table 'INTRODUCTIONS' entry at index {Index} is not a valid table (config plugin id: {ConfigPluginId}).", i, this.Id); + continue; + } + + if (DataIntroduction.TryParseConfiguration(i, luaIntroductionTable, this.Id, out var introduction)) + this.introductions.Add(introduction); + else + LOG.LogWarning("The table 'INTRODUCTIONS' entry at index {Index} does not contain a valid introduction (config plugin id: {ConfigPluginId}).", i, this.Id); + } + } } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index f7a5aabf..391288cf 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -214,6 +214,10 @@ public static partial class PluginFactory if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.StartPage, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + // Check for the built-in introduction visibility: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ShowIntroduction, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + // Check for the quick start guide visibility: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.App, x => x.ShowQuickStartGuide, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index ce0ae866..9efa9e9b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -136,4 +136,13 @@ public static partial class PluginFactory .SelectMany(plugin => plugin.MandatoryInfos) .ToList(); } + + public static IReadOnlyList GetIntroductions() + { + return RUNNING_PLUGINS + .OfType() + .SelectMany(plugin => plugin.Introductions) + .OrderBy(introduction => introduction.Index) + .ToList(); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md index a93e1895..f8aeb0ca 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.2.md @@ -1,4 +1,5 @@ # v26.6.2, build 242 (2026-06-xx xx:xx UTC) - Added a read-only view for organization-managed profiles and chat templates, so users can inspect the content while the organization remains in control of changes. +- Added support for organization-managed introduction texts on the home page. Configuration plugins can now add custom Markdown introductions and hide the built-in introduction. - Fixed organization-managed chat templates not showing the correct icon in the chat template selection menu. - Fixed self-hosted provider API keys sometimes being stored under a localized name. AI Studio now uses a stable key name, keeps correct entries working, and automatically migrates known localized entries for LLM, transcription, and embedding providers. Organizations using configuration plugins do not need to change their plugins; affected users who still see an invalid API key warning should open the provider, transcription, or embedding settings and update the API key once. \ No newline at end of file