Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4cde99
Add I18N assistant for localization of AI Studio content
SommerEngineering Apr 24, 2025
2af61be
Refactor Lua export functionality in the I18N key collection command
SommerEngineering Apr 25, 2025
85c93ce
Added content API to language plugins to access all the data
SommerEngineering Apr 25, 2025
6df07dd
Enhance plugin loading by making the plugin path optional
SommerEngineering Apr 25, 2025
93b488a
Made the localization assistant depend on the selected preview features
SommerEngineering Apr 25, 2025
9a339c6
Fix key formatting in I18N key collection command by replacing dots w…
SommerEngineering Apr 25, 2025
a84cfa1
Added the language plugin option in I18N settings
SommerEngineering Apr 25, 2025
bf47329
Add language plugin selection and loading feedback in I18N assistant
SommerEngineering Apr 25, 2025
35c2891
Add IETF tag conversion for CommonLanguages enum
SommerEngineering Apr 25, 2025
3528535
Enhance language selection with validation and feedback for the local…
SommerEngineering Apr 25, 2025
bb7eead
Resolved warnings
SommerEngineering Apr 25, 2025
9423af6
Spelling
SommerEngineering Apr 26, 2025
3e31d0a
Rename variables for clarification
SommerEngineering Apr 26, 2025
eba3e7a
Refactor Lua string escaping to a new utility class
SommerEngineering Apr 26, 2025
12d36a9
Refactor UI_TEXT_CONTENT table generation into a new LuaTable utility…
SommerEngineering Apr 26, 2025
125c3c1
Add DisabledActionParam to ButtonData for conditional button disabling
SommerEngineering Apr 26, 2025
fdfea15
Refactor button click handling to use async Start method for improved…
SommerEngineering Apr 26, 2025
d4dd368
Finished I18N assistant
SommerEngineering Apr 26, 2025
9fee515
Add support for comment content in LuaTable.Create method
SommerEngineering Apr 26, 2025
78414f3
Update the localized content section to use the total item count
SommerEngineering Apr 26, 2025
5a974fb
Clear final Lua code and localized content on language change for imp…
SommerEngineering Apr 26, 2025
d13e42c
Enhance Lua code generation by including comment using the base language
SommerEngineering Apr 26, 2025
2254e93
Updated changelog
SommerEngineering Apr 26, 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
182 changes: 58 additions & 124 deletions app/Build/Commands/CollectI18NKeysCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public async Task CollectI18NKeys()
Console.WriteLine($" {counter:###,###} files processed, {allI18NContent.Count:###,###} keys found.");

Console.Write("- Creating Lua code ...");
var luaCode = this.ExportToLuaTable(allI18NContent);
var luaCode = this.ExportToLuaAssignments(allI18NContent);

// Build the path, where we want to store the Lua code:
var luaPath = Path.Join(cwd, "Assistants", "I18N", "allTexts.lua");
Expand All @@ -69,134 +69,68 @@ public async Task CollectI18NKeys()

Console.WriteLine(" done.");
}
private string ExportToLuaTable(Dictionary<string, string> keyValuePairs)

private string ExportToLuaAssignments(Dictionary<string, string> keyValuePairs)
{
// Collect all nodes:
var root = new Dictionary<string, object>();

//
// Split all collected keys into nodes:
//
foreach (var key in keyValuePairs.Keys.Order())
{
var path = key.Split('.');
var current = root;
for (var i = 0; i < path.Length - 1; i++)
{
// We ignore the AISTUDIO segment of the path:
if(path[i] == "AISTUDIO")
continue;

if (!current.TryGetValue(path[i], out var child) || child is not Dictionary<string, object> childDict)
{
childDict = new Dictionary<string, object>();
current[path[i]] = childDict;
}

current = childDict;
}

current[path.Last()] = keyValuePairs[key];
}
var sb = new StringBuilder();

//
// Inner method to build Lua code from the collected nodes:
//
void ToLuaTable(StringBuilder sb, Dictionary<string, object> innerDict, int indent = 0)
{
sb.AppendLine("{");
var prefix = new string(' ', indent * 4);
foreach (var kvp in innerDict)
{
if (kvp.Value is Dictionary<string, object> childDict)
{
sb.Append($"{prefix} {kvp.Key}");
sb.Append(" = ");

ToLuaTable(sb, childDict, indent + 1);
}
else if (kvp.Value is string s)
{
sb.AppendLine($"{prefix} -- {s.Trim().Replace("\n", " ")}");
sb.Append($"{prefix} {kvp.Key}");
sb.Append(" = ");
sb.Append($"""
"{s}"
""");
sb.AppendLine(",");
sb.AppendLine();
}
}
// Add the mandatory plugin metadata:
sb.AppendLine(
"""
-- The ID for this plugin:
ID = "77c2688a-a68f-45cc-820e-fa8f3038a146"

sb.AppendLine(prefix + "},");
sb.AppendLine();
}

//
// Write the Lua code:
//
var sbLua = new StringBuilder();

// To make the later parsing easier, we add the mandatory plugin
// metadata:
sbLua.AppendLine(
"""
-- The ID for this plugin:
ID = "77c2688a-a68f-45cc-820e-fa8f3038a146"

-- The icon for the plugin:
ICON_SVG = ""

-- The name of the plugin:
NAME = "Collected I18N keys"

-- The description of the plugin:
DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project."

-- 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" }

-- 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 = ""

-- The IETF BCP 47 tag for the language. It's the ISO 639 language
-- code followed by the ISO 3166-1 country code:
IETF_TAG = "en-US"

-- The language name in the user's language:
LANG_NAME = "English (United States)"

""");

sbLua.Append("UI_TEXT_CONTENT = ");
if(root["UI_TEXT_CONTENT"] is Dictionary<string, object> dict)
ToLuaTable(sbLua, dict);
-- The icon for the plugin:
ICON_SVG = ""

-- The name of the plugin:
NAME = "Collected I18N keys"

-- The description of the plugin:
DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project."

-- 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" }

-- 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 = ""

-- The IETF BCP 47 tag for the language. It's the ISO 639 language
-- code followed by the ISO 3166-1 country code:
IETF_TAG = "en-US"

-- The language name in the user's language:
LANG_NAME = "English (United States)"

"""
);

return sbLua.ToString();
// Add the UI_TEXT_CONTENT table:
LuaTable.Create(ref sb, "UI_TEXT_CONTENT", keyValuePairs);
return sb.ToString();
}

private List<string> FindAllTextTags(ReadOnlySpan<char> fileContent)
{
const string START_TAG = """
Expand Down
1 change: 1 addition & 0 deletions app/MindWork AI Studio.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RID/@EntryIndexedValue">RID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=I18N/@EntryIndexedValue">I18N</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
Expand Down
6 changes: 3 additions & 3 deletions app/MindWork AI Studio/Assistants/AssistantBase.razor
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
</CascadingValue>

<MudStack Row="true" AlignItems="AlignItems.Center" StretchItems="StretchItems.Start" Class="mb-3">
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" OnClick="() => this.SubmitAction()" Style="@this.SubmitButtonStyle">
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" OnClick="async () => await this.Start()" Style="@this.SubmitButtonStyle">
@this.SubmitText
</MudButton>
@if (this.isProcessing && this.cancellationTokenSource is not null)
Expand Down Expand Up @@ -97,14 +97,14 @@
{
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
<MudTooltip Text="@buttonData.Tooltip">
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" Disabled="@buttonData.DisabledAction()" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
@buttonData.Text
</MudButton>
</MudTooltip>
break;

case ButtonData buttonData:
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" Disabled="@buttonData.DisabledAction()" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
@buttonData.Text
</MudButton>
break;
Expand Down
30 changes: 18 additions & 12 deletions app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase, IMe
protected Profile currentProfile = Profile.NO_PROFILE;
protected ChatThread? chatThread;
protected IContent? lastUserPrompt;
protected CancellationTokenSource? cancellationTokenSource;

private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));

private CancellationTokenSource? cancellationTokenSource;

private ContentBlock? resultingContentBlock;
private string[] inputIssues = [];
private bool isProcessing;
Expand Down Expand Up @@ -179,6 +179,16 @@ public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEv
return null;
}

private async Task Start()
{
using (this.cancellationTokenSource = new())
{
await this.SubmitAction();
}

this.cancellationTokenSource = null;
}

private void TriggerFormChange(FormFieldChangedEventArgs _)
{
this.formChangeTimer.Stop();
Expand Down Expand Up @@ -286,16 +296,12 @@ protected async Task<string> AddAIResponseAsync(DateTimeOffset time, bool hideCo

this.isProcessing = true;
this.StateHasChanged();

using (this.cancellationTokenSource = new())
{
// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
// content to be streamed.
this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource.Token);
}

this.cancellationTokenSource = null;

// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
// content to be streamed.
this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource!.Token);

this.isProcessing = false;
this.StateHasChanged();

Expand Down
Loading