AI action panel improvements.

This commit is contained in:
Burak Kaan Köse
2026-04-04 01:34:57 +02:00
parent 1211e9b28a
commit 1667aa34db
12 changed files with 330 additions and 87 deletions
+1 -1
View File
@@ -33,7 +33,7 @@
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.82.1" /> <PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.82.1" />
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" /> <PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.1" /> <PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.1" />
<PackageVersion Include="Wino.Mail.Contracts" Version="1.0.14" /> <PackageVersion Include="Wino.Mail.Contracts" Version="1.0.15" />
<PackageVersion Include="MimeKit" Version="4.15.1" /> <PackageVersion Include="MimeKit" Version="4.15.1" />
<PackageVersion Include="morelinq" Version="4.4.0" /> <PackageVersion Include="morelinq" Version="4.4.0" />
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" /> <PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
@@ -67,6 +67,21 @@ public interface IPreferencesService : INotifyPropertyChanged
/// </summary> /// </summary>
bool IsWinoAccountButtonHidden { get; set; } bool IsWinoAccountButtonHidden { get; set; }
/// <summary>
/// Setting: Default target language code used for AI translation actions.
/// </summary>
string AiDefaultTranslationLanguageCode { get; set; }
/// <summary>
/// Setting: Preferred target language code for AI summarize actions.
/// </summary>
string AiSummarizeLanguageCode { get; set; }
/// <summary>
/// Setting: Preferred folder path used when saving AI summaries.
/// </summary>
string AiSummarySavePath { get; set; }
/// <summary> /// <summary>
/// Serializes the current syncable preferences snapshot. /// Serializes the current syncable preferences snapshot.
/// </summary> /// </summary>
@@ -19,7 +19,7 @@ public interface IWinoAccountApiClient
Task<ApiEnvelope<JsonElement>> LogoutAsync(string refreshToken, CancellationToken cancellationToken = default); Task<ApiEnvelope<JsonElement>> LogoutAsync(string refreshToken, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default);
Task<ApiEnvelope<WinoStoreCollectionsIdTicketInfo>> CreateCollectionsIdTicketAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<WinoStoreCollectionsIdTicketInfo>> CreateCollectionsIdTicketAsync(CancellationToken cancellationToken = default);
@@ -24,7 +24,7 @@ public interface IWinoAccountProfileService
Task<bool> HasActiveAccountAsync(); Task<bool> HasActiveAccountAsync();
Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<AuthUserDto>> GetCurrentUserAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default);
Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default); Task<ApiEnvelope<AiTextResultDto>> RewriteAsync(string html, string mode, CancellationToken cancellationToken = default);
Task<ApiEnvelope<JsonElement>> SyncStoreEntitlementsAsync(CancellationToken cancellationToken = default); Task<ApiEnvelope<JsonElement>> SyncStoreEntitlementsAsync(CancellationToken cancellationToken = default);
@@ -724,6 +724,16 @@
"SettingsAppPreferences_HideWinoAccountButton_Description": "Hide the title bar profile button that opens the Wino account flyout.", "SettingsAppPreferences_HideWinoAccountButton_Description": "Hide the title bar profile button that opens the Wino account flyout.",
"SettingsAppPreferences_StoreUpdateNotifications_Title": "Store update notifications", "SettingsAppPreferences_StoreUpdateNotifications_Title": "Store update notifications",
"SettingsAppPreferences_StoreUpdateNotifications_Description": "Show notifications and footer actions when a Microsoft Store update is available.", "SettingsAppPreferences_StoreUpdateNotifications_Description": "Show notifications and footer actions when a Microsoft Store update is available.",
"SettingsAppPreferences_AiActions_Title": "AI actions",
"SettingsAppPreferences_AiActions_Description": "Choose default AI languages and where summaries should be saved.",
"SettingsAppPreferences_AiDefaultTranslationLanguage_Title": "Default translation language",
"SettingsAppPreferences_AiDefaultTranslationLanguage_Description": "Pick the default target language used by AI translate actions.",
"SettingsAppPreferences_AiSummarizeLanguage_Title": "Summarize language",
"SettingsAppPreferences_AiSummarizeLanguage_Description": "Choose the preferred summarize language for future AI summary output.",
"SettingsAppPreferences_AiSummarySavePath_Title": "Default summary save path",
"SettingsAppPreferences_AiSummarySavePath_Description": "Choose the folder Wino should use by default when saving AI summaries.",
"SettingsAppPreferences_AiSummarySavePath_Placeholder": "Use the system default save location",
"SettingsAppPreferences_AiSummarySavePath_InvalidHint": "This folder does not exist. The default save location will be used for summaries.",
"SettingsAutoSelectNextItem_Description": "Select the next item after you delete or move a mail.", "SettingsAutoSelectNextItem_Description": "Select the next item after you delete or move a mail.",
"SettingsAutoSelectNextItem_Title": "Auto select next item", "SettingsAutoSelectNextItem_Title": "Auto select next item",
"SettingsAvailableThemes_Description": "Select a theme from Wino's own collection for your taste or apply your own themes.", "SettingsAvailableThemes_Description": "Select a theme from Wino's own collection for your taste or apply your own themes.",
@@ -1071,7 +1081,7 @@
"AiActions_SignedOutDescription": "Translate, rewrite, and summarize emails with AI after signing in to your Wino Account and activating the AI Pack add-on.", "AiActions_SignedOutDescription": "Translate, rewrite, and summarize emails with AI after signing in to your Wino Account and activating the AI Pack add-on.",
"AiActions_NoPackTitle": "AI Pack required", "AiActions_NoPackTitle": "AI Pack required",
"AiActions_NoPackDescription": "You're signed in, but AI Pack is not active yet. Purchase it to use Wino's AI translation, rewrite, and summarize tools.", "AiActions_NoPackDescription": "You're signed in, but AI Pack is not active yet. Purchase it to use Wino's AI translation, rewrite, and summarize tools.",
"AiActions_UsageSummary": "{0} of {1} requests used this month.", "AiActions_UsageSummary": "{0} of {1} credits used this month.",
"Composer_AiRewritePolite": "Make it polite", "Composer_AiRewritePolite": "Make it polite",
"Composer_AiRewritePoliteDescription": "Softens the wording while keeping the same intent.", "Composer_AiRewritePoliteDescription": "Softens the wording while keeping the same intent.",
"Composer_AiRewriteAngry": "Make it angry", "Composer_AiRewriteAngry": "Make it angry",
@@ -1243,7 +1253,7 @@
"WinoAccount_Management_AiPackPromoTitle": "Unlock AI Pack", "WinoAccount_Management_AiPackPromoTitle": "Unlock AI Pack",
"WinoAccount_Management_AiPackPromoDescription": "Supercharge your email workflow with AI-powered tools. Translate messages into 50+ languages, rewrite for clarity and tone, and get instant summaries of long threads.", "WinoAccount_Management_AiPackPromoDescription": "Supercharge your email workflow with AI-powered tools. Translate messages into 50+ languages, rewrite for clarity and tone, and get instant summaries of long threads.",
"WinoAccount_Management_AiPackPromoPrice": "$4.99 / mo", "WinoAccount_Management_AiPackPromoPrice": "$4.99 / mo",
"WinoAccount_Management_AiPackPromoRequests": "1,000 requests", "WinoAccount_Management_AiPackPromoRequests": "1,000 credits",
"WinoAccount_Management_AiPackGetButton": "Get AI Pack", "WinoAccount_Management_AiPackGetButton": "Get AI Pack",
"WinoAddOn_AI_PACK_Name": "Wino AI Pack", "WinoAddOn_AI_PACK_Name": "Wino AI Pack",
"WinoAddOn_AI_PACK_Description": "AI-powered tools for translate, rewrite, and summarize actions in Wino Mail.", "WinoAddOn_AI_PACK_Description": "AI-powered tools for translate, rewrite, and summarize actions in Wino Mail.",
@@ -1256,7 +1266,7 @@
"WinoAccount_Management_StoreSyncFailed": "Your purchase finished, but Wino could not refresh your account benefits yet. Please try again in a moment.", "WinoAccount_Management_StoreSyncFailed": "Your purchase finished, but Wino could not refresh your account benefits yet. Please try again in a moment.",
"WinoAccount_Management_AiPackSubscriptionActive": "Your subscription is active", "WinoAccount_Management_AiPackSubscriptionActive": "Your subscription is active",
"WinoAccount_Management_AiPackRenews": "Renews {0:d}", "WinoAccount_Management_AiPackRenews": "Renews {0:d}",
"WinoAccount_Management_AiPackRequestsUsed": "Requests used this month", "WinoAccount_Management_AiPackRequestsUsed": "Credits used this month",
"WinoAccount_Management_AiPackResets": "Resets {0:d}", "WinoAccount_Management_AiPackResets": "Resets {0:d}",
"WinoAccount_Management_AiPackUsageLoadFailed": "We had issues loading your AI usage balance.", "WinoAccount_Management_AiPackUsageLoadFailed": "We had issues loading your AI usage balance.",
"WinoAccount_Management_AiPackFeatureTranslate": "Translate", "WinoAccount_Management_AiPackFeatureTranslate": "Translate",
@@ -1,10 +1,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Ai;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Translations; using Wino.Core.Domain.Models.Translations;
@@ -16,12 +18,14 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
IMailDialogService dialogService, IMailDialogService dialogService,
IPreferencesService preferencesService, IPreferencesService preferencesService,
IStartupBehaviorService startupBehaviorService, IStartupBehaviorService startupBehaviorService,
ITranslationService translationService) ITranslationService translationService,
IAiActionOptionsService aiActionOptionsService)
{ {
_dialogService = dialogService; _dialogService = dialogService;
PreferencesService = preferencesService; PreferencesService = preferencesService;
_startupBehaviorService = startupBehaviorService; _startupBehaviorService = startupBehaviorService;
_translationService = translationService; _translationService = translationService;
_aiActionOptionsService = aiActionOptionsService;
SearchModes = SearchModes =
[ [
@@ -40,6 +44,7 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
SelectedDefaultSearchMode = SearchModes[(int)PreferencesService.DefaultSearchMode]; SelectedDefaultSearchMode = SearchModes[(int)PreferencesService.DefaultSearchMode];
SelectedDefaultApplicationMode = ApplicationModes[(int)PreferencesService.DefaultApplicationMode]; SelectedDefaultApplicationMode = ApplicationModes[(int)PreferencesService.DefaultApplicationMode];
EmailSyncIntervalMinutes = PreferencesService.EmailSyncIntervalMinutes; EmailSyncIntervalMinutes = PreferencesService.EmailSyncIntervalMinutes;
SummarySavePath = PreferencesService.AiSummarySavePath;
} }
public IPreferencesService PreferencesService { get; } public IPreferencesService PreferencesService { get; }
@@ -56,6 +61,21 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
[ObservableProperty] [ObservableProperty]
public partial AppLanguageModel SelectedLanguage { get; set; } public partial AppLanguageModel SelectedLanguage { get; set; }
[ObservableProperty]
public partial List<AiTranslateLanguageOption> AvailableAiLanguages { get; set; } = [];
[ObservableProperty]
public partial AiTranslateLanguageOption SelectedDefaultTranslationLanguage { get; set; }
[ObservableProperty]
public partial AiTranslateLanguageOption SelectedSummarizeLanguage { get; set; }
[ObservableProperty]
public partial string SummarySavePath { get; set; } = string.Empty;
[ObservableProperty]
public partial bool HasInvalidSummarySavePath { get; set; }
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorDisabled))] [NotifyPropertyChangedFor(nameof(IsStartupBehaviorDisabled))]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorEnabled))] [NotifyPropertyChangedFor(nameof(IsStartupBehaviorEnabled))]
@@ -64,7 +84,9 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
private readonly IMailDialogService _dialogService; private readonly IMailDialogService _dialogService;
private readonly IStartupBehaviorService _startupBehaviorService; private readonly IStartupBehaviorService _startupBehaviorService;
private readonly ITranslationService _translationService; private readonly ITranslationService _translationService;
private readonly IAiActionOptionsService _aiActionOptionsService;
private bool _isLanguageInitialized; private bool _isLanguageInitialized;
private bool _isAiPreferencesInitialized;
private int _emailSyncIntervalMinutes; private int _emailSyncIntervalMinutes;
private string _selectedDefaultSearchMode; private string _selectedDefaultSearchMode;
private string _selectedDefaultApplicationMode; private string _selectedDefaultApplicationMode;
@@ -110,6 +132,31 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
_ = _translationService.InitializeLanguageAsync(value.Language); _ = _translationService.InitializeLanguageAsync(value.Language);
} }
partial void OnSelectedDefaultTranslationLanguageChanged(AiTranslateLanguageOption value)
{
if (!_isAiPreferencesInitialized || value == null)
return;
PreferencesService.AiDefaultTranslationLanguageCode = value.Code;
}
partial void OnSelectedSummarizeLanguageChanged(AiTranslateLanguageOption value)
{
if (!_isAiPreferencesInitialized || value == null)
return;
PreferencesService.AiSummarizeLanguageCode = value.Code;
}
partial void OnSummarySavePathChanged(string value)
{
if (!_isAiPreferencesInitialized)
return;
PreferencesService.AiSummarySavePath = value ?? string.Empty;
RefreshSummarySavePathState();
}
[RelayCommand] [RelayCommand]
private async Task ToggleStartupBehaviorAsync() private async Task ToggleStartupBehaviorAsync()
{ {
@@ -157,15 +204,56 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
} }
} }
[RelayCommand]
private async Task BrowseSummarySavePathAsync()
{
var pickedPath = await _dialogService.PickWindowsFolderAsync();
if (string.IsNullOrWhiteSpace(pickedPath))
return;
await ExecuteUIThread(() => SummarySavePath = pickedPath);
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters) public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
AvailableLanguages = _translationService.GetAvailableLanguages(); var availableLanguages = _translationService.GetAvailableLanguages();
SelectedLanguage = AvailableLanguages.Find(language => language.Language == PreferencesService.CurrentLanguage) var availableAiLanguages = new List<AiTranslateLanguageOption>(_aiActionOptionsService.GetTranslateLanguageOptions());
?? (AvailableLanguages.Count > 0 ? AvailableLanguages[0] : null); var startupBehaviorResult = await _startupBehaviorService.GetCurrentStartupBehaviorAsync();
_isLanguageInitialized = true;
StartupBehaviorResult = await _startupBehaviorService.GetCurrentStartupBehaviorAsync(); await ExecuteUIThread(() =>
{
AvailableLanguages = availableLanguages;
SelectedLanguage = AvailableLanguages.Find(language => language.Language == PreferencesService.CurrentLanguage)
?? (AvailableLanguages.Count > 0 ? AvailableLanguages[0] : null);
_isLanguageInitialized = true;
AvailableAiLanguages = availableAiLanguages;
SelectedDefaultTranslationLanguage = FindAiLanguageOption(PreferencesService.AiDefaultTranslationLanguageCode)
?? FindAiLanguageOption("en-US")
?? (AvailableAiLanguages.Count > 0 ? AvailableAiLanguages[0] : null);
SelectedSummarizeLanguage = FindAiLanguageOption(PreferencesService.AiSummarizeLanguageCode)
?? FindAiLanguageOption("en-US")
?? (AvailableAiLanguages.Count > 0 ? AvailableAiLanguages[0] : null);
SummarySavePath = PreferencesService.AiSummarySavePath;
RefreshSummarySavePathState();
_isAiPreferencesInitialized = true;
StartupBehaviorResult = startupBehaviorResult;
});
}
private AiTranslateLanguageOption FindAiLanguageOption(string languageCode)
{
if (string.IsNullOrWhiteSpace(languageCode))
return null;
return AvailableAiLanguages.Find(option => option.Code == languageCode);
}
private void RefreshSummarySavePathState()
{
HasInvalidSummarySavePath = !string.IsNullOrWhiteSpace(SummarySavePath) && !Directory.Exists(SummarySavePath);
} }
} }
+80 -68
View File
@@ -143,71 +143,50 @@
Style="{StaticResource AccentButtonStyle}" /> Style="{StaticResource AccentButtonStyle}" />
</StackPanel> </StackPanel>
<StackPanel <Grid
x:Name="ReadyPanel" x:Name="ReadyPanel"
Spacing="8" ColumnSpacing="12"
Visibility="Collapsed"> Visibility="Collapsed">
<!-- Row 1: Action tabs + usage --> <Grid.ColumnDefinitions>
<Grid ColumnSpacing="12"> <ColumnDefinition Width="Auto" />
<Grid.ColumnDefinitions> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> </Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <!-- Column 1: Action tabs -->
</Grid.ColumnDefinitions> <controls:Segmented
x:Name="ActionSelector"
Height="30"
VerticalAlignment="Center"
SelectionChanged="ActionSelector_SelectionChanged"
Style="{StaticResource ButtonSegmentedStyle}">
<controls:SegmentedItem
x:Name="TranslateSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Switch" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
<controls:SegmentedItem
x:Name="RewriteSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Edit" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
<controls:SegmentedItem
x:Name="SummarizeSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Bullets" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
</controls:Segmented>
<controls:Segmented <!-- Column 2: Action-specific options -->
x:Name="ActionSelector" <Grid Grid.Column="1" ColumnSpacing="8">
Height="30"
VerticalAlignment="Center"
SelectionChanged="ActionSelector_SelectionChanged"
Style="{StaticResource ButtonSegmentedStyle}">
<controls:SegmentedItem
x:Name="TranslateSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Switch" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
<controls:SegmentedItem
x:Name="RewriteSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Edit" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
<controls:SegmentedItem
x:Name="SummarizeSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize, Mode=OneWay}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Bullets" />
</controls:SegmentedItem.Icon>
</controls:SegmentedItem>
</controls:Segmented>
<StackPanel
Grid.Column="2"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<ProgressBar
x:Name="UsageProgressBar"
Width="100"
VerticalAlignment="Center"
Maximum="1000"
Value="0" />
<TextBlock
x:Name="UsageSummaryTextBlock"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Grid>
<!-- Row 2: Action-specific options -->
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -284,11 +263,24 @@
x:Name="SummarizeOptionsPanel" x:Name="SummarizeOptionsPanel"
Spacing="8" Spacing="8"
Visibility="Collapsed"> Visibility="Collapsed">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}" <StackPanel Orientation="Horizontal" Spacing="8">
Style="{StaticResource CaptionTextBlockStyle}" <TextBlock
Text="{x:Bind domain:Translator.Composer_AiSummarizeDescription, Mode=OneWay}" /> VerticalAlignment="Center"
<StackPanel Orientation="Horizontal" Spacing="4"> Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage, Mode=OneWay}" />
<ComboBox
x:Name="SummarizeLanguageComboBox"
MinWidth="120"
SelectionChanged="SummarizeLanguageComboBox_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:AiTranslateLanguageOption">
<TextBlock Text="{x:Bind Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button <Button
x:Name="RunSummarizeButton" x:Name="RunSummarizeButton"
HorizontalAlignment="Left" HorizontalAlignment="Left"
@@ -306,10 +298,30 @@
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiSummarize, Mode=OneWay}" ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiSummarize, Mode=OneWay}"
Visibility="Collapsed" /> Visibility="Collapsed" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel>
<!-- Column 3: Progress -->
<StackPanel
Grid.Column="2"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<ProgressBar
x:Name="UsageProgressBar"
Width="100"
VerticalAlignment="Center"
Maximum="1000"
Value="0" />
<TextBlock
x:Name="UsageSummaryTextBlock"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Grid>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
@@ -25,6 +25,7 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
private readonly IStoreManagementService _storeManagementService = App.Current.Services.GetRequiredService<IStoreManagementService>(); private readonly IStoreManagementService _storeManagementService = App.Current.Services.GetRequiredService<IStoreManagementService>();
private readonly IMailDialogService _dialogService = App.Current.Services.GetRequiredService<IMailDialogService>(); private readonly IMailDialogService _dialogService = App.Current.Services.GetRequiredService<IMailDialogService>();
private readonly IAiActionOptionsService _optionsService = App.Current.Services.GetRequiredService<IAiActionOptionsService>(); private readonly IAiActionOptionsService _optionsService = App.Current.Services.GetRequiredService<IAiActionOptionsService>();
private readonly IPreferencesService _preferencesService = App.Current.Services.GetRequiredService<IPreferencesService>();
private bool _disposedValue; private bool _disposedValue;
private bool _isRefreshing; private bool _isRefreshing;
@@ -43,6 +44,8 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
public AiTranslateLanguageOption? SelectedTranslateLanguageOption { get; set; } public AiTranslateLanguageOption? SelectedTranslateLanguageOption { get; set; }
public AiTranslateLanguageOption? SelectedSummarizeLanguageOption { get; set; }
public AiRewriteModeOption? SelectedRewriteModeOption { get; set; } public AiRewriteModeOption? SelectedRewriteModeOption { get; set; }
public AiActionsPanel() public AiActionsPanel()
@@ -90,19 +93,33 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
{ {
// Save current selections before replacing ItemsSource (which clears SelectedItem). // Save current selections before replacing ItemsSource (which clears SelectedItem).
var previousTranslateCode = SelectedTranslateLanguageOption?.Code; var previousTranslateCode = SelectedTranslateLanguageOption?.Code;
var previousSummarizeCode = SelectedSummarizeLanguageOption?.Code;
var previousRewriteMode = SelectedRewriteModeOption?.Mode; var previousRewriteMode = SelectedRewriteModeOption?.Mode;
var preferredTranslateCode = string.IsNullOrWhiteSpace(previousTranslateCode)
? _preferencesService.AiDefaultTranslationLanguageCode
: previousTranslateCode;
var preferredSummarizeCode = string.IsNullOrWhiteSpace(previousSummarizeCode)
? _preferencesService.AiSummarizeLanguageCode
: previousSummarizeCode;
_translateOptions = _optionsService.GetTranslateLanguageOptions(); _translateOptions = _optionsService.GetTranslateLanguageOptions();
_rewriteOptions = _optionsService.GetRewriteModeOptions(); _rewriteOptions = _optionsService.GetRewriteModeOptions();
TranslateLanguageComboBox.ItemsSource = _translateOptions; TranslateLanguageComboBox.ItemsSource = _translateOptions;
SummarizeLanguageComboBox.ItemsSource = _translateOptions;
RewriteModeComboBox.ItemsSource = _rewriteOptions; RewriteModeComboBox.ItemsSource = _rewriteOptions;
// Restore selection by matching on value, falling back to first item. // Restore selection by matching on value, falling back to first item.
SelectedTranslateLanguageOption = FindOption(_translateOptions, o => o.Code == previousTranslateCode) ?? (_translateOptions.Count > 0 ? _translateOptions[0] : null); SelectedTranslateLanguageOption = FindOption(_translateOptions, o => o.Code == preferredTranslateCode)
?? FindOption(_translateOptions, o => o.Code == "en-US")
?? (_translateOptions.Count > 0 ? _translateOptions[0] : null);
SelectedSummarizeLanguageOption = FindOption(_translateOptions, o => o.Code == preferredSummarizeCode)
?? FindOption(_translateOptions, o => o.Code == "en-US")
?? (_translateOptions.Count > 0 ? _translateOptions[0] : null);
SelectedRewriteModeOption = FindOption(_rewriteOptions, o => o.Mode == previousRewriteMode) ?? (_rewriteOptions.Count > 0 ? _rewriteOptions[0] : null); SelectedRewriteModeOption = FindOption(_rewriteOptions, o => o.Mode == previousRewriteMode) ?? (_rewriteOptions.Count > 0 ? _rewriteOptions[0] : null);
TranslateLanguageComboBox.SelectedItem = SelectedTranslateLanguageOption; TranslateLanguageComboBox.SelectedItem = SelectedTranslateLanguageOption;
SummarizeLanguageComboBox.SelectedItem = SelectedSummarizeLanguageOption;
RewriteModeComboBox.SelectedItem = SelectedRewriteModeOption; RewriteModeComboBox.SelectedItem = SelectedRewriteModeOption;
UpdateRewriteOptionState(); UpdateRewriteOptionState();
} }
@@ -293,6 +310,7 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
BusyProgressBar.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed; BusyProgressBar.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
ActionSelector.IsEnabled = !isBusy; ActionSelector.IsEnabled = !isBusy;
TranslateLanguageComboBox.IsEnabled = !isBusy; TranslateLanguageComboBox.IsEnabled = !isBusy;
SummarizeLanguageComboBox.IsEnabled = !isBusy;
RewriteModeComboBox.IsEnabled = !isBusy; RewriteModeComboBox.IsEnabled = !isBusy;
CustomRewriteTextBox.IsEnabled = !isBusy; CustomRewriteTextBox.IsEnabled = !isBusy;
RunTranslateButton.IsEnabled = !isBusy; RunTranslateButton.IsEnabled = !isBusy;
@@ -426,6 +444,7 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
if (TranslateLanguageComboBox.SelectedItem is AiTranslateLanguageOption option) if (TranslateLanguageComboBox.SelectedItem is AiTranslateLanguageOption option)
{ {
SelectedTranslateLanguageOption = option; SelectedTranslateLanguageOption = option;
_preferencesService.AiDefaultTranslationLanguageCode = option.Code;
} }
} }
@@ -438,6 +457,15 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
} }
} }
private void SummarizeLanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SummarizeLanguageComboBox.SelectedItem is AiTranslateLanguageOption option)
{
SelectedSummarizeLanguageOption = option;
_preferencesService.AiSummarizeLanguageCode = option.Code;
}
}
private void UpdateRewriteOptionState() private void UpdateRewriteOptionState()
{ {
var isCustom = SelectedRewriteModeOption?.IsCustom ?? false; var isCustom = SelectedRewriteModeOption?.IsCustom ?? false;
@@ -500,6 +528,12 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
return; return;
} }
if (action == AiActionType.Summarize && SelectedSummarizeLanguageOption == null)
{
_dialogService.InfoBarMessage(Translator.Composer_AiErrorTitle, Translator.WinoAccount_Error_ValidationFailed, InfoBarMessageType.Error);
return;
}
if (action == AiActionType.Translate) if (action == AiActionType.Translate)
{ {
var cachedTranslation = await HtmlHost.TryGetCachedTranslationHtmlAsync(SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true); var cachedTranslation = await HtmlHost.TryGetCachedTranslationHtmlAsync(SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true);
@@ -539,7 +573,7 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
{ {
AiActionType.Translate => await _profileService.TranslateAsync(html, SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true), AiActionType.Translate => await _profileService.TranslateAsync(html, SelectedTranslateLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true),
AiActionType.Rewrite => await _profileService.RewriteAsync(html, ResolveRewriteMode(), cancellationToken).ConfigureAwait(true), AiActionType.Rewrite => await _profileService.RewriteAsync(html, ResolveRewriteMode(), cancellationToken).ConfigureAwait(true),
AiActionType.Summarize => await _profileService.SummarizeAsync(html, cancellationToken).ConfigureAwait(true), AiActionType.Summarize => await _profileService.SummarizeAsync(html, SelectedSummarizeLanguageOption?.Code ?? string.Empty, cancellationToken).ConfigureAwait(true),
_ => ApiEnvelope<AiTextResultDto>.Failure(ApiErrorCodes.ValidationFailed) _ => ApiEnvelope<AiTextResultDto>.Failure(ApiErrorCodes.ValidationFailed)
}; };
@@ -684,7 +718,11 @@ public sealed partial class AiActionsPanel : UserControl, IDisposable
try try
{ {
var path = await _dialogService.PickFilePathAsync(HtmlHost.GetSuggestedSummaryFileName()); var configuredSummarySavePath = _preferencesService.AiSummarySavePath;
var path = !string.IsNullOrWhiteSpace(configuredSummarySavePath) && Directory.Exists(configuredSummarySavePath)
? Path.Combine(configuredSummarySavePath, HtmlHost.GetSuggestedSummaryFileName())
: await _dialogService.PickFilePathAsync(HtmlHost.GetSuggestedSummaryFileName());
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
{ {
args.Cancel = true; args.Cancel = true;
@@ -389,6 +389,24 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
set => SetPropertyAndSave(nameof(IsWinoAccountButtonHidden), value); set => SetPropertyAndSave(nameof(IsWinoAccountButtonHidden), value);
} }
public string AiDefaultTranslationLanguageCode
{
get => _configurationService.Get(nameof(AiDefaultTranslationLanguageCode), "en-US");
set => SetPropertyAndSave(nameof(AiDefaultTranslationLanguageCode), string.IsNullOrWhiteSpace(value) ? "en-US" : value.Trim());
}
public string AiSummarizeLanguageCode
{
get => _configurationService.Get(nameof(AiSummarizeLanguageCode), "en-US");
set => SetPropertyAndSave(nameof(AiSummarizeLanguageCode), string.IsNullOrWhiteSpace(value) ? "en-US" : value.Trim());
}
public string AiSummarySavePath
{
get => _configurationService.Get(nameof(AiSummarySavePath), string.Empty);
set => SetPropertyAndSave(nameof(AiSummarySavePath), value?.Trim() ?? string.Empty);
}
public WinoApplicationMode DefaultApplicationMode public WinoApplicationMode DefaultApplicationMode
{ {
get get
@@ -7,6 +7,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain" xmlns:domain="using:Wino.Core.Domain"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:Wino.Core.Domain.Models.Ai"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:translations="using:Wino.Core.Domain.Models.Translations" xmlns:translations="using:Wino.Core.Domain.Models.Translations"
mc:Ignorable="d"> mc:Ignorable="d">
@@ -74,6 +75,67 @@
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" /> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE896;" />
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsExpander
Description="{x:Bind domain:Translator.SettingsAppPreferences_AiActions_Description}"
Header="{x:Bind domain:Translator.SettingsAppPreferences_AiActions_Title}"
IsExpanded="True">
<controls:SettingsExpander.HeaderIcon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE734;" />
</controls:SettingsExpander.HeaderIcon>
<controls:SettingsExpander.Items>
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_AiDefaultTranslationLanguage_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_AiDefaultTranslationLanguage_Title}">
<ComboBox ItemsSource="{x:Bind ViewModel.AvailableAiLanguages, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedDefaultTranslationLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:AiTranslateLanguageOption">
<TextBlock Text="{x:Bind Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarizeLanguage_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarizeLanguage_Title}">
<ComboBox ItemsSource="{x:Bind ViewModel.AvailableAiLanguages, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedSummarizeLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:AiTranslateLanguageOption">
<TextBlock Text="{x:Bind Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard
Description="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_Description}"
Header="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_Title}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ContentAlignment="Vertical">
<StackPanel Spacing="8">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
IsReadOnly="True"
PlaceholderText="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_Placeholder}"
Text="{x:Bind ViewModel.SummarySavePath, Mode=OneWay}" />
<Button
Grid.Column="1"
Command="{x:Bind ViewModel.BrowseSummarySavePathCommand}"
Content="{x:Bind domain:Translator.Buttons_Browse}" />
</Grid>
<TextBlock
Foreground="{ThemeResource SystemFillColorCautionBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_InvalidHint}"
TextWrapping="WrapWholeWords"
Visibility="{x:Bind ViewModel.HasInvalidSummarySavePath, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel> </StackPanel>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
+2 -2
View File
@@ -108,11 +108,11 @@ public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable
public Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default) public Task<ApiEnvelope<AiStatusResultDto>> GetAiStatusAsync(CancellationToken cancellationToken = default)
=> SendAuthorizedRequestAsync("api/v1/ai/status", WinoAccountApiJsonContext.Default.ApiEnvelopeAiStatusResultDto, cancellationToken); => SendAuthorizedRequestAsync("api/v1/ai/status", WinoAccountApiJsonContext.Default.ApiEnvelopeAiStatusResultDto, cancellationToken);
public Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, CancellationToken cancellationToken = default) public Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default)
=> SendAuthorizedRequestAsync( => SendAuthorizedRequestAsync(
HttpMethod.Post, HttpMethod.Post,
"api/v1/ai/summarize", "api/v1/ai/summarize",
new SummarizeRequest(html), new SummarizeRequest(html, targetLanguage),
WinoAccountApiJsonContext.Default.SummarizeRequest, WinoAccountApiJsonContext.Default.SummarizeRequest,
WinoAccountApiJsonContext.Default.ApiEnvelopeAiTextResultDto, WinoAccountApiJsonContext.Default.ApiEnvelopeAiTextResultDto,
cancellationToken); cancellationToken);
+2 -2
View File
@@ -213,8 +213,8 @@ public sealed class WinoAccountProfileService : BaseDatabaseService, IWinoAccoun
return response; return response;
} }
public async Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, CancellationToken cancellationToken = default) public async Task<ApiEnvelope<AiTextResultDto>> SummarizeAsync(string html, string targetLanguage, CancellationToken cancellationToken = default)
=> await ExecuteAiOperationAsync(account => _apiClient.SummarizeAsync(html, cancellationToken), "summarize", cancellationToken).ConfigureAwait(false); => await ExecuteAiOperationAsync(account => _apiClient.SummarizeAsync(html, targetLanguage, cancellationToken), "summarize", cancellationToken).ConfigureAwait(false);
public async Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default) public async Task<ApiEnvelope<AiTextResultDto>> TranslateAsync(string html, string targetLanguage, CancellationToken cancellationToken = default)
=> await ExecuteAiOperationAsync(account => _apiClient.TranslateAsync(html, targetLanguage, cancellationToken), "translate", cancellationToken).ConfigureAwait(false); => await ExecuteAiOperationAsync(account => _apiClient.TranslateAsync(html, targetLanguage, cancellationToken), "translate", cancellationToken).ConfigureAwait(false);