Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
445fc57
Add IETFTag property to language plugin interface and implementation
SommerEngineering Apr 12, 2025
7f6190a
Add LangName property to language plugin interface and implementation
SommerEngineering Apr 12, 2025
900b65e
Add language behavior to the app settings
SommerEngineering Apr 12, 2025
03292a0
Fix formatting and spacing
SommerEngineering Apr 12, 2025
fe9e7ea
Add documentation
SommerEngineering Apr 12, 2025
1c4da1c
Refactor variable names for clarity
SommerEngineering Apr 12, 2025
42845ea
Improved parallel plugin management
SommerEngineering Apr 12, 2025
f1dc717
Fixed the plugin factory setup; it runs after the data directory is k…
SommerEngineering Apr 12, 2025
6e8f9e5
Added the plugin's path to the metadata
SommerEngineering Apr 12, 2025
e2fc761
Added indexer to language plugins
SommerEngineering Apr 12, 2025
a8663e4
Added a no language plugin class
SommerEngineering Apr 12, 2025
adcd199
Start plugins
SommerEngineering Apr 12, 2025
55c472d
Provide the active language plugin
SommerEngineering Apr 12, 2025
3506d3b
Add FNVHash class for 32-bit and 64-bit hash computations
SommerEngineering Apr 12, 2025
d4cfc99
Add ILang interface for language plugin text access
SommerEngineering Apr 12, 2025
dc427af
Updated plugin
SommerEngineering Apr 12, 2025
022cc9d
Added an option for logging of unknown translation keys
SommerEngineering Apr 12, 2025
7271520
Updated
SommerEngineering Apr 12, 2025
383f9ef
Fixed base language plugin not being treated as a running plugin
SommerEngineering Apr 12, 2025
592cec1
Spelling
SommerEngineering Apr 12, 2025
8331e68
Improved HTTP client's set accessibility
SommerEngineering Apr 12, 2025
9e18b8f
Changed base class
SommerEngineering Apr 12, 2025
6280287
Improved message handling methods by making them virtual, i.e., optio…
SommerEngineering Apr 12, 2025
5dc3e0d
Implemented I18N handling
SommerEngineering Apr 12, 2025
2ba4789
Migrated some text to use I18N
SommerEngineering Apr 12, 2025
7a56ec6
Removed indexer from language plugin classes
SommerEngineering Apr 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/MindWork AI Studio.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EDI/@EntryIndexedValue">EDI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ERI/@EntryIndexedValue">ERI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FNV/@EntryIndexedValue">FNV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
Expand Down
6 changes: 3 additions & 3 deletions app/MindWork AI Studio/Components/ChatComponent.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,8 @@ private Task EditLastBlock(IContent block)
#region Overrides of MSGComponentBase

public override string ComponentName => nameof(ChatComponent);
public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default

protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
Expand All @@ -860,7 +860,7 @@ private Task EditLastBlock(IContent block)
}
}

public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
protected override Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
{
switch (triggeredEvent)
{
Expand Down
9 changes: 2 additions & 7 deletions app/MindWork AI Studio/Components/InnerScrolling.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ protected override async Task OnInitializedAsync()
#region Overrides of MSGComponentBase

public override string ComponentName => nameof(InnerScrolling);
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default

protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
Expand All @@ -59,11 +59,6 @@ protected override async Task OnInitializedAsync()
return Task.CompletedTask;
}

public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
{
return Task.FromResult(default(TResult));
}

#endregion

private string MinWidthStyle => string.IsNullOrWhiteSpace(this.MinWidth) ? string.Empty : $"min-width: {this.MinWidth}; ";
Expand Down
66 changes: 54 additions & 12 deletions app/MindWork AI Studio/Components/MSGComponentBase.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;

using Microsoft.AspNetCore.Components;

namespace AIStudio.Components;

public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver, ILang
{
[Inject]
protected SettingsManager SettingsManager { get; init; } = null!;

[Inject]
protected MessageBus MessageBus { get; init; } = null!;

[Inject]
private ILogger<PluginLanguage> Logger { get; init; } = null!;

private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage;

#region Overrides of ComponentBase

protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();

this.MessageBus.RegisterComponent(this);
base.OnInitialized();
await base.OnInitializedAsync();
}

#endregion

#region Implementation of ILang

/// <inheritdoc />
public string T(string fallbackEN)
{
var type = this.GetType();
var ns = $"{type.Namespace!}::{type.Name}".ToUpperInvariant().Replace(".", "::");
var key = $"root::{ns}::T{fallbackEN.ToFNV32()}";

if(this.Lang.TryGetText(key, out var text, logWarning: false))
return text;

this.Logger.LogWarning($"Missing translation key '{key}' for content '{fallbackEN}'.");
return fallbackEN;
}

#endregion
Expand All @@ -26,27 +52,42 @@ protected override void OnInitialized()

public abstract string ComponentName { get; }

public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
switch (triggeredEvent)
{
case Event.COLOR_THEME_CHANGED:
this.StateHasChanged();
break;

case Event.PLUGINS_RELOADED:
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();
await this.InvokeAsync(this.StateHasChanged);
break;

default:
return this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
await this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
break;
}

return Task.CompletedTask;
}

public abstract Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);

public abstract Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data);

public async Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return await this.ProcessIncomingMessageWithResult<TPayload, TResult>(sendingComponent, triggeredEvent, data);
}

#endregion

protected virtual Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
{
return Task.CompletedTask;
}

protected virtual Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
{
return Task.FromResult<TResult?>(default);
}

#region Implementation of IDisposable

public void Dispose()
Expand All @@ -71,7 +112,8 @@ protected void ApplyFilters(ComponentBase[] components, Event[] events)
// Append the color theme changed event to the list of events:
var eventsList = new List<Event>(events)
{
Event.COLOR_THEME_CHANGED
Event.COLOR_THEME_CHANGED,
Event.PLUGINS_RELOADED,
};

this.MessageBus.ApplyFilters(this, components, eventsList.ToArray());
Expand Down
19 changes: 15 additions & 4 deletions app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,33 @@
@inherits SettingsPanelBase

<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options">

@if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="Language behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguageBehavior)" Data="@ConfigurationSelectDataFactory.GetLangBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.LanguageBehavior = selectedValue)" OptionHelp="Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?"/>

@if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL)
{
<ConfigurationSelect OptionDescription="Language" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.LanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.LanguagePluginId = selectedValue)" OptionHelp="Select the language for the app."/>
}
}

<ConfigurationSelect OptionDescription="Color theme" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreferredTheme)" Data="@ConfigurationSelectDataFactory.GetThemesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreferredTheme = selectedValue)" OptionHelp="Choose the color theme that best suits for you."/>
<ConfigurationOption OptionDescription="Save energy?" LabelOn="Energy saving is enabled" LabelOff="Energy saving is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.IsSavingEnergy)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.IsSavingEnergy = updatedState)" OptionHelp="When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."/>
<ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." />
<ConfigurationOption OptionDescription="Enable spellchecking?" LabelOn="Spellchecking is enabled" LabelOff="Spellchecking is disabled" State="@(() => this.SettingsManager.ConfigurationData.App.EnableSpellchecking)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.EnableSpellchecking = updatedState)" OptionHelp="When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections."/>
<ConfigurationSelect OptionDescription="Check for updates" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateBehavior)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateBehavior = selectedValue)" OptionHelp="How often should we check for app updates?"/>
<ConfigurationSelect OptionDescription="Navigation bar behavior" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="Select the desired behavior for the navigation bar."/>
<ConfigurationSelect OptionDescription="Preview feature visibility" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@this.UpdatePreviewFeatures" OptionHelp="Do you want to show preview features in the app?"/>
@if(this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)

@if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
{
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
if (availablePreviewFeatures.Count > 0)
{
<ConfigurationMultiSelect OptionDescription="Select preview features" SelectedValues="@(() => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures)" Data="@availablePreviewFeatures" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedValue)" OptionHelp="Which preview features would you like to enable?"/>
}
}

<ConfigurationProviderSelection Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.")"/>
<ConfigurationSelect OptionDescription="Preselect one of your profiles?" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence."/>
</ExpansionPanel>
52 changes: 34 additions & 18 deletions app/MindWork AI Studio/Layout/MainLayout.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,13 @@ protected override async Task OnInitializedAsync()
// Ensure that all settings are loaded:
await this.SettingsManager.LoadSettings();

//
// We cannot process the plugins before the settings are loaded,
// and we know our data directory.
//
if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
{
// Ensure that all internal plugins are present:
await PluginFactory.EnsureInternalPlugins();

// Load (but not start) all plugins, without waiting for them:
var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
_ = PluginFactory.LoadAll(pluginLoadingTimeout.Token);

// Set up hot reloading for plugins:
PluginFactory.SetUpHotReloading();
}

// Register this component with the message bus:
this.MessageBus.RegisterComponent(this);
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR ]);
this.MessageBus.ApplyFilters(this, [],
[
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED
]);

// Set the snackbar for the update service:
UpdateService.SetBlazorDependencies(this.Snackbar);
Expand All @@ -115,6 +102,9 @@ protected override async Task OnInitializedAsync()
// Solve issue https://github.com/MudBlazor/MudBlazor/issues/11133:
MudGlobal.TooltipDefaults.Duration = TimeSpan.Zero;

// Send a message to start the plugin system:
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_PLUGIN_SYSTEM);

await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged);
await this.UpdateThemeConfiguration();
this.LoadNavItems();
Expand Down Expand Up @@ -179,6 +169,32 @@ public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event trigg
error.Show(this.Snackbar);

break;

case Event.STARTUP_PLUGIN_SYSTEM:
if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
{
_ = Task.Run(async () =>
{
// Set up the plugin system:
PluginFactory.Setup();

// Ensure that all internal plugins are present:
await PluginFactory.EnsureInternalPlugins();

// Load (but not start) all plugins, without waiting for them:
var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await PluginFactory.LoadAll(pluginLoadingTimeout.Token);

// Set up hot reloading for plugins:
PluginFactory.SetUpHotReloading();
});
}

break;

case Event.PLUGINS_RELOADED:
await this.InvokeAsync(this.StateHasChanged);
break;
}
}

Expand Down
4 changes: 2 additions & 2 deletions app/MindWork AI Studio/Pages/Chat.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
@if (this.chatThread is not null && this.chatThread.WorkspaceId != Guid.Empty)
{
@($"Chat in Workspace \"{this.currentWorkspaceName}\"")
@(T("Chat in Workspace") + $" \"{this.currentWorkspaceName}\"")
}
else
{
@("Temporary Chat")
@(T("Short-Term Chat"))
}
</MudText>

Expand Down
9 changes: 2 additions & 7 deletions app/MindWork AI Studio/Pages/Chat.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ private void UpdateWorkspaceName(string workspaceName)
#region Overrides of MSGComponentBase

public override string ComponentName => nameof(Chat);
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default

protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
Expand All @@ -96,10 +96,5 @@ private void UpdateWorkspaceName(string workspaceName)
return Task.CompletedTask;
}

public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
{
return Task.FromResult(default(TResult));
}

#endregion
}
5 changes: 4 additions & 1 deletion app/MindWork AI Studio/Pages/Home.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
@attribute [Route(Routes.HOME)]
@inherits MSGComponentBase

<div class="inner-scrolling-context">
<MudImage Src="svg/banner.svg" Style="max-height: 16em; width: 100%; object-fit: cover;" />
<MudText Typo="Typo.h3" Class="mt-2 mb-2">Let's get started</MudText>
<MudText Typo="Typo.h3" Class="mt-2 mb-2">
@T("Let's get started")
</MudText>

<InnerScrolling>
<MudExpansionPanels Class="mb-3" MultiExpansion="@false">
Expand Down
12 changes: 9 additions & 3 deletions app/MindWork AI Studio/Pages/Home.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

namespace AIStudio.Pages;

public partial class Home : ComponentBase
public partial class Home : MSGComponentBase
{
[Inject]
private HttpClient HttpClient { get; set; } = null!;
private HttpClient HttpClient { get; init; } = null!;

private string LastChangeContent { get; set; } = string.Empty;

Expand All @@ -22,7 +22,13 @@ protected override async Task OnInitializedAsync()
}

#endregion


#region Overrides of MSGComponentBase

public override string ComponentName => nameof(Home);

#endregion

private async Task ReadLastChangeAsync()
{
var latest = Changelog.LOGS.MaxBy(n => n.Build);
Expand Down
1 change: 1 addition & 0 deletions app/MindWork AI Studio/Pages/Plugins.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@using AIStudio.Tools.PluginSystem
@inherits MSGComponentBase
@attribute [Route(Routes.PLUGINS)]

<div class="inner-scrolling-context">
Expand Down
Loading