Skip to content

Commit 3fc15d9

Browse files
Configure language & start language plugins (#400)
1 parent 171ed37 commit 3fc15d9

31 files changed

+612
-156
lines changed

app/MindWork AI Studio.sln.DotSettings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
33
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EDI/@EntryIndexedValue">EDI</s:String>
44
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ERI/@EntryIndexedValue">ERI</s:String>
5+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FNV/@EntryIndexedValue">FNV</s:String>
56
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
67
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
78
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>

app/MindWork AI Studio/Components/ChatComponent.razor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,8 @@ private Task EditLastBlock(IContent block)
840840
#region Overrides of MSGComponentBase
841841

842842
public override string ComponentName => nameof(ChatComponent);
843-
844-
public override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
843+
844+
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
845845
{
846846
switch (triggeredEvent)
847847
{
@@ -860,7 +860,7 @@ private Task EditLastBlock(IContent block)
860860
}
861861
}
862862

863-
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
863+
protected override Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
864864
{
865865
switch (triggeredEvent)
866866
{

app/MindWork AI Studio/Components/InnerScrolling.razor.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ protected override async Task OnInitializedAsync()
4646
#region Overrides of MSGComponentBase
4747

4848
public override string ComponentName => nameof(InnerScrolling);
49-
50-
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
49+
50+
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
5151
{
5252
switch (triggeredEvent)
5353
{
@@ -59,11 +59,6 @@ protected override async Task OnInitializedAsync()
5959
return Task.CompletedTask;
6060
}
6161

62-
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
63-
{
64-
return Task.FromResult(default(TResult));
65-
}
66-
6762
#endregion
6863

6964
private string MinWidthStyle => string.IsNullOrWhiteSpace(this.MinWidth) ? string.Empty : $"min-width: {this.MinWidth}; ";

app/MindWork AI Studio/Components/MSGComponentBase.cs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
11
using AIStudio.Settings;
2+
using AIStudio.Tools.PluginSystem;
23

34
using Microsoft.AspNetCore.Components;
45

56
namespace AIStudio.Components;
67

7-
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
8+
public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver, ILang
89
{
910
[Inject]
1011
protected SettingsManager SettingsManager { get; init; } = null!;
1112

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

16+
[Inject]
17+
private ILogger<PluginLanguage> Logger { get; init; } = null!;
18+
19+
private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage;
20+
1521
#region Overrides of ComponentBase
1622

17-
protected override void OnInitialized()
23+
protected override async Task OnInitializedAsync()
1824
{
25+
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();
26+
1927
this.MessageBus.RegisterComponent(this);
20-
base.OnInitialized();
28+
await base.OnInitializedAsync();
29+
}
30+
31+
#endregion
32+
33+
#region Implementation of ILang
34+
35+
/// <inheritdoc />
36+
public string T(string fallbackEN)
37+
{
38+
var type = this.GetType();
39+
var ns = $"{type.Namespace!}::{type.Name}".ToUpperInvariant().Replace(".", "::");
40+
var key = $"root::{ns}::T{fallbackEN.ToFNV32()}";
41+
42+
if(this.Lang.TryGetText(key, out var text, logWarning: false))
43+
return text;
44+
45+
this.Logger.LogWarning($"Missing translation key '{key}' for content '{fallbackEN}'.");
46+
return fallbackEN;
2147
}
2248

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

2753
public abstract string ComponentName { get; }
2854

29-
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
55+
public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
3056
{
3157
switch (triggeredEvent)
3258
{
3359
case Event.COLOR_THEME_CHANGED:
3460
this.StateHasChanged();
3561
break;
3662

63+
case Event.PLUGINS_RELOADED:
64+
this.Lang = await this.SettingsManager.GetActiveLanguagePlugin();
65+
await this.InvokeAsync(this.StateHasChanged);
66+
break;
67+
3768
default:
38-
return this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
69+
await this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
70+
break;
3971
}
40-
41-
return Task.CompletedTask;
4272
}
43-
44-
public abstract Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
45-
46-
public abstract Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data);
73+
74+
public async Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
75+
{
76+
return await this.ProcessIncomingMessageWithResult<TPayload, TResult>(sendingComponent, triggeredEvent, data);
77+
}
4778

4879
#endregion
4980

81+
protected virtual Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
82+
{
83+
return Task.CompletedTask;
84+
}
85+
86+
protected virtual Task<TResult?> ProcessIncomingMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
87+
{
88+
return Task.FromResult<TResult?>(default);
89+
}
90+
5091
#region Implementation of IDisposable
5192

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

77119
this.MessageBus.ApplyFilters(this, components, eventsList.ToArray());

app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,33 @@
33
@inherits SettingsPanelBase
44

55
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="App Options">
6+
7+
@if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
8+
{
9+
<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?"/>
10+
11+
@if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL)
12+
{
13+
<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."/>
14+
}
15+
}
16+
617
<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."/>
718
<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."/>
8-
<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." />
19+
<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."/>
920
<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?"/>
1021
<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."/>
1122
<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?"/>
12-
13-
@if(this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
23+
24+
@if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
1425
{
1526
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
1627
if (availablePreviewFeatures.Count > 0)
1728
{
1829
<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?"/>
1930
}
2031
}
21-
32+
2233
<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.")"/>
2334
<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."/>
2435
</ExpansionPanel>

app/MindWork AI Studio/Layout/MainLayout.razor.cs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,26 +83,13 @@ protected override async Task OnInitializedAsync()
8383
// Ensure that all settings are loaded:
8484
await this.SettingsManager.LoadSettings();
8585

86-
//
87-
// We cannot process the plugins before the settings are loaded,
88-
// and we know our data directory.
89-
//
90-
if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
91-
{
92-
// Ensure that all internal plugins are present:
93-
await PluginFactory.EnsureInternalPlugins();
94-
95-
// Load (but not start) all plugins, without waiting for them:
96-
var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
97-
_ = PluginFactory.LoadAll(pluginLoadingTimeout.Token);
98-
99-
// Set up hot reloading for plugins:
100-
PluginFactory.SetUpHotReloading();
101-
}
102-
10386
// Register this component with the message bus:
10487
this.MessageBus.RegisterComponent(this);
105-
this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR ]);
88+
this.MessageBus.ApplyFilters(this, [],
89+
[
90+
Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR,
91+
Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED
92+
]);
10693

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

105+
// Send a message to start the plugin system:
106+
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_PLUGIN_SYSTEM);
107+
118108
await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged);
119109
await this.UpdateThemeConfiguration();
120110
this.LoadNavItems();
@@ -179,6 +169,32 @@ public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event trigg
179169
error.Show(this.Snackbar);
180170

181171
break;
172+
173+
case Event.STARTUP_PLUGIN_SYSTEM:
174+
if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager))
175+
{
176+
_ = Task.Run(async () =>
177+
{
178+
// Set up the plugin system:
179+
PluginFactory.Setup();
180+
181+
// Ensure that all internal plugins are present:
182+
await PluginFactory.EnsureInternalPlugins();
183+
184+
// Load (but not start) all plugins, without waiting for them:
185+
var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
186+
await PluginFactory.LoadAll(pluginLoadingTimeout.Token);
187+
188+
// Set up hot reloading for plugins:
189+
PluginFactory.SetUpHotReloading();
190+
});
191+
}
192+
193+
break;
194+
195+
case Event.PLUGINS_RELOADED:
196+
await this.InvokeAsync(this.StateHasChanged);
197+
break;
182198
}
183199
}
184200

app/MindWork AI Studio/Pages/Chat.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
88
@if (this.chatThread is not null && this.chatThread.WorkspaceId != Guid.Empty)
99
{
10-
@($"Chat in Workspace \"{this.currentWorkspaceName}\"")
10+
@(T("Chat in Workspace") + $" \"{this.currentWorkspaceName}\"")
1111
}
1212
else
1313
{
14-
@("Temporary Chat")
14+
@(T("Short-Term Chat"))
1515
}
1616
</MudText>
1717

app/MindWork AI Studio/Pages/Chat.razor.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ private void UpdateWorkspaceName(string workspaceName)
8383
#region Overrides of MSGComponentBase
8484

8585
public override string ComponentName => nameof(Chat);
86-
87-
public override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
86+
87+
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
8888
{
8989
switch (triggeredEvent)
9090
{
@@ -96,10 +96,5 @@ private void UpdateWorkspaceName(string workspaceName)
9696
return Task.CompletedTask;
9797
}
9898

99-
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
100-
{
101-
return Task.FromResult(default(TResult));
102-
}
103-
10499
#endregion
105100
}

app/MindWork AI Studio/Pages/Home.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
@attribute [Route(Routes.HOME)]
2+
@inherits MSGComponentBase
23

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

710
<InnerScrolling>
811
<MudExpansionPanels Class="mb-3" MultiExpansion="@false">

app/MindWork AI Studio/Pages/Home.razor.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
namespace AIStudio.Pages;
88

9-
public partial class Home : ComponentBase
9+
public partial class Home : MSGComponentBase
1010
{
1111
[Inject]
12-
private HttpClient HttpClient { get; set; } = null!;
12+
private HttpClient HttpClient { get; init; } = null!;
1313

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

@@ -22,7 +22,13 @@ protected override async Task OnInitializedAsync()
2222
}
2323

2424
#endregion
25-
25+
26+
#region Overrides of MSGComponentBase
27+
28+
public override string ComponentName => nameof(Home);
29+
30+
#endregion
31+
2632
private async Task ReadLastChangeAsync()
2733
{
2834
var latest = Changelog.LOGS.MaxBy(n => n.Build);

0 commit comments

Comments
 (0)