mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 22:52:10 +00:00
Fixed the I18N assistant
This commit is contained in:
parent
afb96fd3bb
commit
7c1e023fb2
@ -129,6 +129,11 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
/// </summary>
|
||||
protected bool IsAssistantComponentDisposed => this.isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this component has attached an assistant session snapshot.
|
||||
/// </summary>
|
||||
protected bool HasAssistantSession => this.assistantSessionId is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assistant-specific identifier used to distinguish session slots.
|
||||
/// </summary>
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogI18N>
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidatingTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="@T("Custom target language")" SelectionUpdated="_ => this.OnChangedLanguage()" />
|
||||
<ConfigurationSelect OptionDescription="@T("Language plugin used for comparision")" SelectedValue="@(() => this.selectedLanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(async void (id) => await this.OnLanguagePluginChanged(id))" OptionHelp="@T("Select the language plugin used for comparision.")"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidatingTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="@T("Custom target language")" SelectionUpdated="_ => this.OnChangedLanguage()" Disabled="@this.IsProcessing" />
|
||||
<ConfigurationSelect OptionDescription="@T("Language plugin used for comparision")" SelectedValue="@(() => this.selectedLanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(async void (id) => await this.OnLanguagePluginChanged(id))" OptionHelp="@T("Select the language plugin used for comparision.")" Disabled="@(() => this.IsProcessing)"/>
|
||||
@if (this.isLoading)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-6">
|
||||
@ -20,7 +20,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.AddedContentText
|
||||
</MudText>
|
||||
<MudTable Items="@this.addedContent" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<MudTable Items="@this.AddedContentRows" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="@this.searchString" Immediate="true" Placeholder="@T("Search")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"/>
|
||||
</ToolBarContent>
|
||||
@ -50,7 +50,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.RemovedContentText
|
||||
</MudText>
|
||||
<MudTable Items="@this.removedContent" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<MudTable Items="@this.RemovedContentRows" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="@this.searchString" Immediate="true" Placeholder="@T("Search")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"/>
|
||||
</ToolBarContent>
|
||||
@ -94,7 +94,7 @@ else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue))
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.LocalizedContentText
|
||||
</MudText>
|
||||
<MudTable Items="@this.localizedContent" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<MudTable Items="@this.LocalizedContentRows" Hover="@true" Filter="@this.FilterFunc" Class="border-dashed border rounded-lg mb-6">
|
||||
<ToolBarContent>
|
||||
<MudTextField @bind-Value="@this.searchString" Immediate="true" Placeholder="@T("Search")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"/>
|
||||
</ToolBarContent>
|
||||
|
||||
@ -118,6 +118,7 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
private Dictionary<string, string> removedContent = [];
|
||||
private Dictionary<string, string> localizedContent = [];
|
||||
private StringBuilder finalLuaCode = new();
|
||||
private string? activeSystemPromptLanguage;
|
||||
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
|
||||
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
|
||||
private static readonly AssistantSessionStateKey<bool> IS_LOADING_STATE_KEY = new(nameof(isLoading));
|
||||
@ -170,26 +171,34 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
if (this.HasAssistantSession)
|
||||
return;
|
||||
|
||||
await this.OnLanguagePluginChanged(this.selectedLanguagePluginId);
|
||||
await this.LoadData();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string SystemPromptLanguage() => this.selectedTargetLanguage switch
|
||||
private string SystemPromptLanguage() => this.activeSystemPromptLanguage ?? (this.selectedTargetLanguage switch
|
||||
{
|
||||
CommonLanguages.OTHER => this.customTargetLanguage,
|
||||
_ => $"{this.selectedTargetLanguage.Name()}",
|
||||
};
|
||||
});
|
||||
|
||||
private async Task OnLanguagePluginChanged(Guid pluginId)
|
||||
{
|
||||
if (this.IsProcessing)
|
||||
return;
|
||||
|
||||
this.selectedLanguagePluginId = pluginId;
|
||||
await this.OnChangedLanguage();
|
||||
}
|
||||
|
||||
private async Task OnChangedLanguage()
|
||||
{
|
||||
if (this.IsProcessing)
|
||||
return;
|
||||
|
||||
this.finalLuaCode.Clear();
|
||||
this.localizedContent.Clear();
|
||||
this.localizationPossible = false;
|
||||
@ -308,6 +317,21 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
|
||||
private int NumTotalItems => (this.selectedLanguagePlugin?.Content.Count ?? 0) + this.addedContent.Count - this.removedContent.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a stable row snapshot for the added-content table.
|
||||
/// </summary>
|
||||
private KeyValuePair<string, string>[] AddedContentRows => this.addedContent.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a stable row snapshot for the removed-content table.
|
||||
/// </summary>
|
||||
private KeyValuePair<string, string>[] RemovedContentRows => this.removedContent.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a stable row snapshot for the localized-content table.
|
||||
/// </summary>
|
||||
private KeyValuePair<string, string>[] LocalizedContentRows => this.localizedContent.ToArray();
|
||||
|
||||
private string AddedContentText => string.Format(T("Added Content ({0} entries)"), this.addedContent.Count);
|
||||
|
||||
private string RemovedContentText => string.Format(T("Removed Content ({0} entries)"), this.removedContent.Count);
|
||||
@ -326,68 +350,87 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
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();
|
||||
}
|
||||
var addedContentSnapshot = this.addedContent.ToArray();
|
||||
var removedContentSnapshot = this.removedContent.ToArray();
|
||||
var removedContentKeys = removedContentSnapshot.Select(keyValuePair => keyValuePair.Key).ToHashSet(StringComparer.Ordinal);
|
||||
var selectedLanguageContentSnapshot = this.selectedLanguagePlugin.Content.ToArray();
|
||||
var baseLanguageContentSnapshot = PluginFactory.BaseLanguage.Content.ToArray();
|
||||
|
||||
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)
|
||||
this.localizedContent.Clear();
|
||||
this.activeSystemPromptLanguage = this.SystemPromptLanguage();
|
||||
try
|
||||
{
|
||||
if (this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
if (this.selectedTargetLanguage is not CommonLanguages.EN_US)
|
||||
{
|
||||
// Phase 1: Translate added content
|
||||
await this.Phase1TranslateAddedContent(addedContentSnapshot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: no translation needed
|
||||
this.localizedContent = addedContentSnapshot.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if(this.CancellationTokenSource!.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (this.localizedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
//
|
||||
// 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 selectedLanguageContentSnapshot)
|
||||
{
|
||||
if (this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (this.localizedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
if (removedContentKeys.Contains(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
this.localizedContent.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
if(this.CancellationTokenSource!.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (this.removedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
//
|
||||
// Phase 2: Create the Lua code. We want to use the base language
|
||||
// for the comments, though:
|
||||
//
|
||||
var commentContent = addedContentSnapshot.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value, StringComparer.Ordinal);
|
||||
foreach (var keyValuePair in baseLanguageContentSnapshot)
|
||||
{
|
||||
if (this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (removedContentKeys.Contains(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
commentContent.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
}
|
||||
|
||||
this.localizedContent.Add(keyValuePair.Key, keyValuePair.Value);
|
||||
this.Phase2CreateLuaCode(commentContent);
|
||||
}
|
||||
|
||||
if(this.CancellationTokenSource!.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
//
|
||||
// Phase 2: Create the Lua code. We want to use the base language
|
||||
// for the comments, though:
|
||||
//
|
||||
var commentContent = new Dictionary<string, string>(this.addedContent);
|
||||
foreach (var keyValuePair in PluginFactory.BaseLanguage.Content)
|
||||
finally
|
||||
{
|
||||
if (this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (this.removedContent.ContainsKey(keyValuePair.Key))
|
||||
continue;
|
||||
|
||||
commentContent.TryAdd(keyValuePair.Key, keyValuePair.Value);
|
||||
this.activeSystemPromptLanguage = null;
|
||||
}
|
||||
|
||||
this.Phase2CreateLuaCode(commentContent);
|
||||
}
|
||||
|
||||
private async Task Phase1TranslateAddedContent()
|
||||
/// <summary>
|
||||
/// Translates the added text content from a stable snapshot.
|
||||
/// </summary>
|
||||
/// <param name="addedContentSnapshot">The added text entries captured when the job started.</param>
|
||||
/// <returns>A task that completes when all added text entries were translated or cancellation was requested.</returns>
|
||||
private async Task Phase1TranslateAddedContent(KeyValuePair<string, string>[] addedContentSnapshot)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
var minimumTime = TimeSpan.FromMilliseconds(500);
|
||||
foreach (var keyValuePair in this.addedContent)
|
||||
foreach (var keyValuePair in addedContentSnapshot)
|
||||
{
|
||||
if(this.CancellationTokenSource!.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
@ -93,7 +93,7 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
private AssistantSessionIndicatorData? AssistantSessionIndicator => this.AssistantSessionSnapshot?.Status switch
|
||||
{
|
||||
AssistantSessionStatus.RUNNING or AssistantSessionStatus.CANCELING => new(Icons.Material.Filled.ChangeCircle, Color.Info, this.T("Assistant is still running.")),
|
||||
AssistantSessionStatus.COMPLETED => new(Icons.Material.Filled.TaskAlt, Color.Success, this.T("Result is ready.")),
|
||||
AssistantSessionStatus.COMPLETED => new(Icons.Material.Filled.TaskAlt, Color.Success, this.T("The result is ready.")),
|
||||
AssistantSessionStatus.FAILED => new(Icons.Material.Filled.Error, Color.Error, this.T("Assistant failed. Open it to review the result.")),
|
||||
AssistantSessionStatus.CANCELED => new(Icons.Material.Filled.Cancel, Color.Warning, this.T("Assistant was canceled. Open it to review the result.")),
|
||||
_ => null,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
@inherits EnumSelectionBase
|
||||
|
||||
<MudStack Row="@true" Class="mb-3">
|
||||
<MudSelect T="@T" Value="@this.Value" ValueChanged="@this.SelectionChanged" AdornmentIcon="@this.Icon" Adornment="Adornment.Start" Label="@this.Label" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateSelection">
|
||||
<MudSelect T="@T" Value="@this.Value" ValueChanged="@this.SelectionChanged" AdornmentIcon="@this.Icon" Adornment="Adornment.Start" Label="@this.Label" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateSelection" Disabled="@this.Disabled">
|
||||
@foreach (var value in Enum.GetValues<T>())
|
||||
{
|
||||
<MudSelectItem Value="@value">
|
||||
@ -12,6 +12,6 @@
|
||||
</MudSelect>
|
||||
@if (this.AllowOther && this.Value.Equals(this.OtherValue))
|
||||
{
|
||||
<MudTextField T="string" Text="@this.OtherInput" TextChanged="this.OtherValueChanged" Validation="@this.ValidateOther" Label="@this.LabelOther" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Immediate="@true"/>
|
||||
<MudTextField T="string" Text="@this.OtherInput" TextChanged="this.OtherValueChanged" Validation="@this.ValidateOther" Label="@this.LabelOther" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Immediate="@true" Disabled="@this.Disabled"/>
|
||||
}
|
||||
</MudStack>
|
||||
@ -38,6 +38,12 @@ public partial class EnumSelection<T> : EnumSelectionBase where T : struct, Enum
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.ArrowDropDown;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the selection controls are disabled.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom name function for selecting the display name of an enum value.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user