mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-27 15:39:47 +00:00
Finished I18N assistant
This commit is contained in:
parent
fdfea1501c
commit
d4dd368e64
@ -87,4 +87,38 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
|
||||
{
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
}
|
||||
|
||||
@if (this.localizedContent.Count > 0)
|
||||
{
|
||||
<hr style="width: 100%; border-width: 0.25ch;" class="mt-6 mb-6"/>
|
||||
<MudText Typo="Typo.h6">
|
||||
Localized Content (@this.localizedContent.Count entries of @this.addedContent.Count)
|
||||
</MudText>
|
||||
<MudTable Items="@this.localizedContent" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="@this.searchString" Immediate="true" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"/>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Key</MudTh>
|
||||
<MudTh>Text</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<pre style="font-size: 0.8em;">
|
||||
@context.Key
|
||||
</pre>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@context.Value
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Dialogs.Settings;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
namespace AIStudio.Assistants.I18N;
|
||||
|
||||
public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
@ -17,22 +22,55 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
"""
|
||||
TODO
|
||||
$"""
|
||||
# Assignment
|
||||
You are an expert in professional translations from English (US) to {this.SystemPromptLanguage()}.
|
||||
You translate the texts without adding any new information. When necessary, you correct
|
||||
spelling and grammar.
|
||||
|
||||
# Context
|
||||
The texts to be translated come from the open source app "MindWork AI Studio". The goal
|
||||
is to localize the app so that it can be offered in other languages. You will always
|
||||
receive one text at a time. A text may be, for example, for a button, a label, or an
|
||||
explanation within the app. The app "AI Studio" is a desktop app for macOS, Linux,
|
||||
and Windows. Users can use Large Language Models (LLMs) in practical ways in their
|
||||
daily lives with it. The app offers the regular chat mode for which LLMs have become
|
||||
known. However, AI Studio also offers so-called assistants, where users no longer
|
||||
have to prompt.
|
||||
|
||||
# Target Audience
|
||||
The app is intended for everyone, not just IT specialists or scientists. When translating,
|
||||
make sure the texts are easy for everyone to understand.
|
||||
""";
|
||||
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override bool ShowResult => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
protected override bool ShowCopyResult => false;
|
||||
|
||||
protected override bool ShowSendTo => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new ButtonData
|
||||
{
|
||||
Text = "Copy Lua code to clipboard",
|
||||
Icon = Icons.Material.Filled.Extension,
|
||||
Color = Color.Default,
|
||||
AsyncAction = async () => await this.RustService.CopyText2Clipboard(this.Snackbar, this.finalLuaCode.ToString()),
|
||||
DisabledActionParam = () => this.finalLuaCode.Length == 0,
|
||||
},
|
||||
];
|
||||
|
||||
protected override string SubmitText => "Localize AI Studio & generate the Lua code";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.LocalizeTextConntent;
|
||||
protected override Func<Task> SubmitAction => this.LocalizeTextContent;
|
||||
|
||||
protected override bool SubmitDisabled => !this.localizationPossible;
|
||||
|
||||
protected override bool ShowDedicatedProgress => true;
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
if (!this.MightPreselectValues())
|
||||
@ -68,6 +106,8 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
private ILanguagePlugin? selectedLanguagePlugin;
|
||||
private Dictionary<string, string> addedContent = [];
|
||||
private Dictionary<string, string> removedContent = [];
|
||||
private Dictionary<string, string> localizedContent = [];
|
||||
private StringBuilder finalLuaCode = new();
|
||||
|
||||
#region Overrides of AssistantBase<SettingsDialogI18N>
|
||||
|
||||
@ -80,6 +120,12 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
|
||||
#endregion
|
||||
|
||||
private string SystemPromptLanguage() => this.selectedTargetLanguage switch
|
||||
{
|
||||
CommonLanguages.OTHER => this.customTargetLanguage,
|
||||
_ => $"{this.selectedTargetLanguage.Name()}",
|
||||
};
|
||||
|
||||
private async Task OnLanguagePluginChanged(Guid pluginId)
|
||||
{
|
||||
this.selectedLanguagePluginId = pluginId;
|
||||
@ -89,9 +135,17 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
private async Task OnChangedLanguage()
|
||||
{
|
||||
if (PluginFactory.RunningPlugins.FirstOrDefault(n => n is PluginLanguage && n.Id == this.selectedLanguagePluginId) is not PluginLanguage comparisonPlugin)
|
||||
{
|
||||
this.loadingIssue = $"Was not able to load the language plugin for comparison ({this.selectedLanguagePluginId}). Please select a valid, loaded & running language plugin.";
|
||||
this.localizationPossible = false;
|
||||
this.selectedLanguagePlugin = null;
|
||||
}
|
||||
else if (comparisonPlugin.IETFTag != this.selectedTargetLanguage.ToIETFTag())
|
||||
{
|
||||
this.loadingIssue = $"The selected language plugin for comparison uses the IETF tag '{comparisonPlugin.IETFTag}' which does not match the selected target language '{this.selectedTargetLanguage.ToIETFTag()}'. Please select a valid, loaded & running language plugin which matches the target language.";
|
||||
this.localizationPossible = false;
|
||||
this.selectedLanguagePlugin = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.selectedLanguagePlugin = comparisonPlugin;
|
||||
@ -195,7 +249,99 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task LocalizeTextConntent()
|
||||
private async Task LocalizeTextContent()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
if(this.selectedLanguagePlugin is null)
|
||||
return;
|
||||
|
||||
if (this.selectedLanguagePlugin.IETFTag != this.selectedTargetLanguage.ToIETFTag())
|
||||
return;
|
||||
|
||||
this.localizedContent.Clear();
|
||||
if (this.selectedTargetLanguage is not CommonLanguages.EN_US)
|
||||
{
|
||||
// Phase 1: Translate added content
|
||||
await this.Phase1TranslateAddedContent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: no translation needed
|
||||
this.localizedContent = this.addedContent.ToDictionary();
|
||||
}
|
||||
|
||||
if(this.cancellationTokenSource!.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
//
|
||||
// Now, we have localized the added content. Next, we must merge
|
||||
// the localized content with the existing content. However, we
|
||||
// must skip the removed content. We use the localizedContent
|
||||
// dictionary for the final result:
|
||||
//
|
||||
foreach (var keyValuePair in this.selectedLanguagePlugin.Content)
|
||||
{
|
||||
if (this.cancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (this.localizedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
if (this.removedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
this.localizedContent.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
if(this.cancellationTokenSource!.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
// Phase 2: Create the Lua code
|
||||
this.Phase2CreateLuaCode();
|
||||
}
|
||||
|
||||
private async Task Phase1TranslateAddedContent()
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
var minimumTime = TimeSpan.FromMilliseconds(500);
|
||||
foreach (var keyValuePair in this.addedContent)
|
||||
{
|
||||
if(this.cancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
//
|
||||
// We measure the time for each translation.
|
||||
// We do not want to make more than 120 requests
|
||||
// per minute, i.e., 2 requests per second.
|
||||
//
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
//
|
||||
// Translate one text at a time:
|
||||
//
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(keyValuePair.Value);
|
||||
this.localizedContent.Add(keyValuePair.Key, await this.AddAIResponseAsync(time));
|
||||
|
||||
if (this.cancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
//
|
||||
// Ensure that we do not exceed the rate limit of 2 requests per second:
|
||||
//
|
||||
stopwatch.Stop();
|
||||
if (stopwatch.Elapsed < minimumTime)
|
||||
await Task.Delay(minimumTime - stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
private void Phase2CreateLuaCode()
|
||||
{
|
||||
this.finalLuaCode.Clear();
|
||||
LuaTable.Create(ref this.finalLuaCode, "UI_TEXT_CONTENT", this.localizedContent, this.cancellationTokenSource!.Token);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user