Merge branch 'main' into shawn/fixAPPrompttextbox

This commit is contained in:
Shawn Yuan
2025-11-13 09:37:27 +08:00
committed by GitHub
15 changed files with 700 additions and 157 deletions

View File

@@ -7,7 +7,9 @@
<h1 align="center"> <h1 align="center">
<span>Microsoft PowerToys</span> <span>Microsoft PowerToys</span>
</h1> </h1>
<p align="center">
<span align="center">Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.</span>
</p>
<h3 align="center"> <h3 align="center">
<a href="#-installation">Installation</a> <a href="#-installation">Installation</a>
<span> · </span> <span> · </span>
@@ -18,8 +20,10 @@
<a href="#-whats-new">Release notes</a> <a href="#-whats-new">Release notes</a>
</h3> </h3>
<br/><br/> <br/><br/>
Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
<br/><br/> ## 🔨 Utilities
PowerToys includes over 25 utilities to help you customize and optimize your Windows experience:
| | | | | | | |
|---|---|---| |---|---|---|
@@ -37,20 +41,13 @@ Microsoft PowerToys is a collection of utilities that help you customize Windows
## 📋 Installation ## 📋 Installation
For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install). For detailed installation instructions and system requirements, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
Before you begin, make sure your device meets the system requirements:
> [!NOTE]
> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer
> - 64-bit processor: x64 or ARM64
> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup
Choose one of the installation methods below:
But to get started quickly, choose one of the installation methods below:
<br/><br/>
<details open> <details open>
<summary>Download .exe from GitHub</summary> <summary><strong>Download .exe from GitHub</strong></summary>
<br/>
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer. Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
<!-- items that need to be updated release to release --> <!-- items that need to be updated release to release -->
@@ -67,11 +64,11 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve
| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] | | Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] | | Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] | | Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] |
</details> </details>
<details> <details>
<summary>Microsoft Store</summary> <summary><strong>Microsoft Store</strong></summary>
<br/>
You can easily install PowerToys from the Microsoft Store: You can easily install PowerToys from the Microsoft Store:
<p> <p>
<a style="text-decoration:none" href="https://aka.ms/getPowertoys"> <a style="text-decoration:none" href="https://aka.ms/getPowertoys">
@@ -82,10 +79,9 @@ You can easily install PowerToys from the Microsoft Store:
</p> </p>
</details> </details>
<details> <details>
<summary>WinGet</summary> <summary><strong>WinGet</strong></summary>
<br/>
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell: Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
*User scope installer [default]* *User scope installer [default]*
@@ -100,8 +96,8 @@ winget install --scope machine Microsoft.PowerToys -s winget
</details> </details>
<details> <details>
<summary>Other methods</summary> <summary><strong>Other methods</strong></summary>
<br/>
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there. There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
</details> </details>

View File

@@ -163,28 +163,143 @@ namespace AdvancedPaste.Settings
return false; return false;
} }
if (settings.Properties.IsAIEnabled || !LegacyOpenAIKeyExists()) var properties = settings.Properties;
var configuration = properties.PasteAIConfiguration;
if (configuration is null)
{
configuration = new PasteAIConfiguration();
properties.PasteAIConfiguration = configuration;
}
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
{ {
return false; return false;
} }
settings.Properties.IsAIEnabled = true; bool configurationUpdated = false;
return true;
if (hasLegacyProviders)
{
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
} }
private static bool LegacyOpenAIKeyExists() PasteAIProviderDefinition openAIProvider = null;
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
{
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
}
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
{
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
configurationUpdated = true;
}
if (legacyCredential is not null && openAIProvider is not null)
{
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
RemoveLegacyOpenAICredential();
}
bool enabledUpdated = false;
if (!properties.IsAIEnabled && legacyCredential is not null)
{
properties.IsAIEnabled = true;
enabledUpdated = true;
}
return configurationUpdated || enabledUpdated || legacyAdvancedAIConsumed;
}
private static PasswordCredential TryGetLegacyOpenAICredential()
{ {
try try
{ {
PasswordVault vault = new(); PasswordVault vault = new();
return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null; var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
credential?.RetrievePassword();
return credential;
} }
catch (Exception) catch (Exception)
{ {
return false; return null;
} }
} }
private static void RemoveLegacyOpenAICredential()
{
try
{
PasswordVault vault = new();
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
}
catch (Exception)
{
}
}
private static void StoreMigratedOpenAICredential(string providerId, string serviceType, string password)
{
if (string.IsNullOrWhiteSpace(password))
{
return;
}
try
{
var serviceKind = serviceType.ToAIServiceType();
if (serviceKind != AIServiceType.OpenAI)
{
return;
}
string resource = "https://platform.openai.com/api-keys";
string username = $"PowerToys_AdvancedPaste_PasteAI_openai_{NormalizeProviderIdentifier(providerId)}";
PasswordVault vault = new();
TryRemoveCredential(vault, resource, username);
PasswordCredential credential = new(resource, username, password);
vault.Add(credential);
}
catch (Exception ex)
{
Logger.LogError("Failed to migrate legacy OpenAI credential", ex);
}
}
private static void TryRemoveCredential(PasswordVault vault, string credentialResource, string credentialUserName)
{
try
{
PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
vault.Remove(existingCred);
}
catch (Exception)
{
// Credential doesn't exist, which is fine
}
}
private static string NormalizeProviderIdentifier(string providerId)
{
if (string.IsNullOrWhiteSpace(providerId))
{
return "default";
}
var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
}
public async Task SetActiveAIProviderAsync(string providerId) public async Task SetActiveAIProviderAsync(string providerId)
{ {
if (string.IsNullOrWhiteSpace(providerId)) if (string.IsNullOrWhiteSpace(providerId))

View File

@@ -196,10 +196,10 @@ public:
m_enabled = true; m_enabled = true;
Trace::EnableCursorWrap(true); Trace::EnableCursorWrap(true);
if (m_autoActivate) // Always start the mouse hook when the module is enabled
{ // This ensures cursor wrapping is active immediately after enabling
StartMouseHook(); StartMouseHook();
} Logger::info("CursorWrap enabled - mouse hook started");
} }
// Disable the powertoy // Disable the powertoy
@@ -208,6 +208,7 @@ public:
m_enabled = false; m_enabled = false;
Trace::EnableCursorWrap(false); Trace::EnableCursorWrap(false);
StopMouseHook(); StopMouseHook();
Logger::info("CursorWrap disabled - mouse hook stopped");
} }
// Returns if the powertoys is enabled // Returns if the powertoys is enabled

View File

@@ -1812,7 +1812,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
// Check if GIF is selected by comparing the text // Check if GIF is selected by comparing the text
bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0); bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
// if gif is selected set the scaling to the g_recordScaleGIF value otherwise to the g_recordScaleMP4 value // If GIF is selected, set the scaling to the g_RecordScalingGIF value; otherwise to the g_RecordScalingMP4 value
if (isGifSelected) { if (isGifSelected) {
g_RecordScaling = g_RecordScalingGIF; g_RecordScaling = g_RecordScalingGIF;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

After

Width:  |  Height:  |  Size: 565 B

View File

@@ -0,0 +1,205 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// Helper methods for migrating legacy Advanced Paste settings to the updated schema.
/// </summary>
public static class AdvancedPasteMigrationHelper
{
/// <summary>
/// Moves legacy provider configuration snapshots into the strongly-typed providers collection.
/// </summary>
/// <param name="configuration">The configuration instance to migrate.</param>
/// <returns>True if the configuration was modified.</returns>
public static bool MigrateLegacyProviderConfigurations(PasteAIConfiguration configuration)
{
if (configuration is null)
{
return false;
}
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
bool configurationUpdated = false;
if (configuration.LegacyProviderConfigurations is { Count: > 0 })
{
foreach (var entry in configuration.LegacyProviderConfigurations)
{
var result = EnsureProvider(configuration, entry.Key, entry.Value);
configurationUpdated |= result.Updated;
}
configuration.LegacyProviderConfigurations = null;
}
configurationUpdated |= EnsureActiveProviderIsValid(configuration);
return configurationUpdated;
}
/// <summary>
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
/// </summary>
/// <param name="configuration">The configuration instance.</param>
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
{
return EnsureProvider(configuration, AIServiceType.OpenAI.ToConfigurationString(), null);
}
/// <summary>
/// Ensures a provider for the supplied service type exists, optionally applying a legacy snapshot.
/// </summary>
/// <param name="configuration">The configuration instance.</param>
/// <param name="serviceTypeKey">The persisted service type key.</param>
/// <param name="snapshot">An optional snapshot containing legacy values.</param>
/// <returns>The ensured provider and whether the configuration was updated.</returns>
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureProvider(PasteAIConfiguration configuration, string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
{
if (configuration is null)
{
return (null, false);
}
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
var normalizedServiceType = NormalizeServiceType(serviceTypeKey);
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, normalizedServiceType, StringComparison.OrdinalIgnoreCase));
bool configurationUpdated = false;
if (existingProvider is null)
{
existingProvider = CreateProvider(normalizedServiceType, snapshot);
configuration.Providers.Add(existingProvider);
configurationUpdated = true;
}
else if (snapshot is not null)
{
configurationUpdated |= ApplySnapshot(existingProvider, snapshot);
}
configurationUpdated |= EnsureActiveProviderIsValid(configuration, existingProvider);
return (existingProvider, configurationUpdated);
}
private static string NormalizeServiceType(string serviceTypeKey)
{
var serviceType = serviceTypeKey.ToAIServiceType();
return serviceType.ToConfigurationString();
}
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
{
var serviceType = serviceTypeKey.ToAIServiceType();
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
var provider = new PasteAIProviderDefinition
{
ServiceType = serviceTypeKey,
ModelName = !string.IsNullOrWhiteSpace(snapshot?.ModelName) ? snapshot.ModelName : PasteAIProviderDefaults.GetDefaultModelName(serviceType),
EndpointUrl = snapshot?.EndpointUrl ?? string.Empty,
ApiVersion = snapshot?.ApiVersion ?? string.Empty,
DeploymentName = snapshot?.DeploymentName ?? string.Empty,
ModelPath = snapshot?.ModelPath ?? string.Empty,
SystemPrompt = snapshot?.SystemPrompt ?? string.Empty,
ModerationEnabled = snapshot?.ModerationEnabled ?? true,
IsLocalModel = metadata.IsLocalModel,
};
return provider;
}
private static bool ApplySnapshot(PasteAIProviderDefinition provider, AIProviderConfigurationSnapshot snapshot)
{
bool updated = false;
if (!string.IsNullOrWhiteSpace(snapshot.ModelName) && !string.Equals(provider.ModelName, snapshot.ModelName, StringComparison.Ordinal))
{
provider.ModelName = snapshot.ModelName;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.EndpointUrl) && !string.Equals(provider.EndpointUrl, snapshot.EndpointUrl, StringComparison.Ordinal))
{
provider.EndpointUrl = snapshot.EndpointUrl;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.ApiVersion) && !string.Equals(provider.ApiVersion, snapshot.ApiVersion, StringComparison.Ordinal))
{
provider.ApiVersion = snapshot.ApiVersion;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.DeploymentName) && !string.Equals(provider.DeploymentName, snapshot.DeploymentName, StringComparison.Ordinal))
{
provider.DeploymentName = snapshot.DeploymentName;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.ModelPath) && !string.Equals(provider.ModelPath, snapshot.ModelPath, StringComparison.Ordinal))
{
provider.ModelPath = snapshot.ModelPath;
updated = true;
}
if (!string.IsNullOrWhiteSpace(snapshot.SystemPrompt) && !string.Equals(provider.SystemPrompt, snapshot.SystemPrompt, StringComparison.Ordinal))
{
provider.SystemPrompt = snapshot.SystemPrompt;
updated = true;
}
if (provider.ModerationEnabled != snapshot.ModerationEnabled)
{
provider.ModerationEnabled = snapshot.ModerationEnabled;
updated = true;
}
return updated;
}
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
{
if (configuration?.Providers is null || configuration.Providers.Count == 0)
{
if (!string.IsNullOrWhiteSpace(configuration?.ActiveProviderId))
{
configuration.ActiveProviderId = string.Empty;
return true;
}
return false;
}
bool updated = false;
var activeProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase));
if (activeProvider is null)
{
activeProvider = preferredProvider ?? configuration.Providers.First();
configuration.ActiveProviderId = activeProvider.Id;
updated = true;
}
foreach (var provider in configuration.Providers)
{
bool shouldBeActive = string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase);
if (provider.IsActive != shouldBeActive)
{
provider.IsActive = shouldBeActive;
updated = true;
}
}
return updated;
}
}
}

View File

@@ -52,10 +52,68 @@ namespace Microsoft.PowerToys.Settings.UI.Library
_extensionData.Remove("IsOpenAIEnabled"); _extensionData.Remove("IsOpenAIEnabled");
} }
if (_extensionData != null && _extensionData.TryGetValue("IsAdvancedAIEnabled", out var legacyAdvancedElement))
{
bool? legacyValue = legacyAdvancedElement.ValueKind switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Object when legacyAdvancedElement.TryGetProperty("value", out var advancedValueElement) => advancedValueElement.ValueKind switch
{
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => null,
},
_ => null,
};
if (legacyValue.HasValue)
{
LegacyAdvancedAIEnabled = legacyValue.Value;
}
_extensionData.Remove("IsAdvancedAIEnabled");
}
} }
} }
private Dictionary<string, JsonElement> _extensionData; private Dictionary<string, JsonElement> _extensionData;
private bool? _legacyAdvancedAIEnabled;
[JsonPropertyName("IsAdvancedAIEnabled")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public BoolProperty LegacyAdvancedAIEnabledProperty
{
get => null;
set
{
if (value is not null)
{
LegacyAdvancedAIEnabled = value.Value;
}
}
}
[JsonIgnore]
public bool? LegacyAdvancedAIEnabled
{
get => _legacyAdvancedAIEnabled;
private set => _legacyAdvancedAIEnabled = value;
}
public bool TryConsumeLegacyAdvancedAIEnabled(out bool value)
{
if (_legacyAdvancedAIEnabled is bool flag)
{
value = flag;
_legacyAdvancedAIEnabled = null;
return true;
}
value = default;
return false;
}
[JsonConverter(typeof(BoolPropertyJsonConverter))] [JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool ShowCustomPreview { get; set; } public bool ShowCustomPreview { get; set; }

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.PowerToys.Settings.UI.Library
{
/// <summary>
/// Provides default values for Paste AI provider definitions.
/// </summary>
public static class PasteAIProviderDefaults
{
/// <summary>
/// Gets the default model name for a given AI service type.
/// </summary>
public static string GetDefaultModelName(AIServiceType serviceType)
{
return serviceType switch
{
AIServiceType.OpenAI => "gpt-4o",
AIServiceType.AzureOpenAI => "gpt-4o",
AIServiceType.Mistral => "mistral-large-latest",
AIServiceType.Google => "gemini-1.5-pro",
AIServiceType.AzureAIInference => "gpt-4o-mini",
AIServiceType.Ollama => "llama3",
_ => string.Empty,
};
}
}
}

View File

@@ -42,8 +42,9 @@
HorizontalAlignment="Center" /> HorizontalAlignment="Center" />
<TextBlock <TextBlock
x:Name="LoadingStatusTextBlock" x:Name="LoadingStatusTextBlock"
x:Uid="AdvancedPaste_FL_LoadingStatus"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Loading Foundry Local status..."
TextAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </StackPanel>
@@ -56,7 +57,6 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel <StackPanel
x:Name="NoModelsPanel" x:Name="NoModelsPanel"
Grid.Row="0" Grid.Row="0"
@@ -64,24 +64,28 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Orientation="Vertical" Orientation="Vertical"
Spacing="4"> Spacing="4">
<FontIcon FontSize="24" Glyph="&#xE74E;" /> <FontIcon
AutomationProperties.AccessibilityView="Raw"
FontSize="24"
Glyph="&#xF158;" />
<TextBlock <TextBlock
x:Uid="AdvancedPaste_FL_NoModelsDownloaded."
HorizontalAlignment="Center" HorizontalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" Style="{StaticResource BodyStrongTextBlockStyle}"
Text="No models downloaded"
TextAlignment="Center" /> TextAlignment="Center" />
<TextBlock <TextBlock
x:Uid="AdvancedPaste_FL_RunFoundryLocalText.Text"
HorizontalAlignment="Center" HorizontalAlignment="Center"
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Run Foundry Local to download or add a local model below."
TextAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<Button <Button
x:Name="LaunchFoundryModelListButton" x:Name="LaunchFoundryModelListButton"
x:Uid="AdvancedPaste_FL_OpenFoundryModelList"
Margin="0,8,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Click="LaunchFoundryModelListButton_Click" Click="LaunchFoundryModelListButton_Click"
Content="Open Foundry model list"
Style="{StaticResource AccentButtonStyle}" /> Style="{StaticResource AccentButtonStyle}" />
</StackPanel> </StackPanel>
@@ -101,10 +105,10 @@
SelectionChanged="CachedModelsComboBox_SelectionChanged"> SelectionChanged="CachedModelsComboBox_SelectionChanged">
<ComboBox.Header> <ComboBox.Header>
<TextBlock> <TextBlock>
<Run Text="Foundry Local model" /><LineBreak /><Run <Run x:Uid="AdvancedPaste_FL_LocalModel" /><LineBreak /><Run
x:Uid="AdvancedPaste_FL_UseCLIToDownloadModels"
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
Text="Use the Foundry Local CLI to download models that run locally on-device. They'll appear here." />
</TextBlock> </TextBlock>
</ComboBox.Header> </ComboBox.Header>
</ComboBox> </ComboBox>
@@ -114,9 +118,11 @@
MinHeight="32" MinHeight="32"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Click="RefreshModelsButton_Click" Click="RefreshModelsButton_Click"
Style="{StaticResource SubtleButtonStyle}" Style="{StaticResource SubtleButtonStyle}">
ToolTipService.ToolTip="Refresh model list">
<FontIcon FontSize="16" Glyph="&#xE72C;" /> <FontIcon FontSize="16" Glyph="&#xE72C;" />
<ToolTipService.ToolTip>
<TextBlock x:Uid="AdvancedPaste_FL_RefreshModelList" />
</ToolTipService.ToolTip>
</Button> </Button>
</Grid> </Grid>
<StackPanel <StackPanel
@@ -146,24 +152,28 @@
Spacing="8"> Spacing="8">
<Image Width="36" Source="ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg" /> <Image Width="36" Source="ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg" />
<TextBlock <TextBlock
x:Uid="AdvancedPaste_FL_FLNotavailableYet"
HorizontalAlignment="Center"
FontWeight="SemiBold" FontWeight="SemiBold"
Text="Foundry Local is not available on this device yet."
TextAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
x:Uid="AdvancedPaste_FL_StartService"
HorizontalAlignment="Center"
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True" IsTextSelectionEnabled="True"
TextAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap"> TextWrapping="Wrap" />
<Run Text="Start the Foundry Local service before returning to PowerToys." /> <HyperlinkButton
</TextBlock> x:Uid="AdvancedPaste_FL_CLIGuide"
<HyperlinkButton Content="Follow the Foundry Local CLI guide" NavigateUri="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-local/get-started" /> HorizontalAlignment="Center"
NavigateUri="https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started" />
<TextBlock <TextBlock
x:Uid="FoundryLocal_RestartRequiredNote" x:Uid="FoundryLocal_RestartRequiredNote"
HorizontalAlignment="Center"
FontSize="12" FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="Note: After installing the Foundry Local CLI, restart PowerToys to use it."
TextAlignment="Center" TextAlignment="Center"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </StackPanel>

View File

@@ -103,10 +103,7 @@
</StackPanel> </StackPanel>
</tkcontrols:SettingsExpander.Description> </tkcontrols:SettingsExpander.Description>
<tkcontrols:SettingsExpander.ItemsHeader> <tkcontrols:SettingsExpander.ItemsHeader>
<tkcontrols:SettingsCard <tkcontrols:SettingsCard x:Uid="AdvancedPaste_ModelProviders" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
Description="Add online or local models"
Header="Model providers"
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<Button Content="Add model" Style="{StaticResource AccentButtonStyle}"> <Button Content="Add model" Style="{StaticResource AccentButtonStyle}">
<Button.Flyout> <Button.Flyout>
<MenuFlyout x:Name="AddProviderMenuFlyout" Opening="AddProviderMenuFlyout_Opening" /> <MenuFlyout x:Name="AddProviderMenuFlyout" Opening="AddProviderMenuFlyout_Opening" />
@@ -130,16 +127,16 @@
<Button.Flyout> <Button.Flyout>
<MenuFlyout> <MenuFlyout>
<MenuFlyoutItem <MenuFlyoutItem
x:Uid="AdvancedPaste_Edit"
Click="EditPasteAIProviderButton_Click" Click="EditPasteAIProviderButton_Click"
Icon="{ui:FontIcon Glyph=&#xE70F;}" Icon="{ui:FontIcon Glyph=&#xE70F;}"
Tag="{x:Bind}" Tag="{x:Bind}" />
Text="Edit" />
<MenuFlyoutSeparator /> <MenuFlyoutSeparator />
<MenuFlyoutItem <MenuFlyoutItem
x:Uid="AdvancedPaste_Remove"
Click="RemovePasteAIProviderButton_Click" Click="RemovePasteAIProviderButton_Click"
Icon="{ui:FontIcon Glyph=&#xE74D;}" Icon="{ui:FontIcon Glyph=&#xE74D;}"
Tag="{x:Bind}" Tag="{x:Bind}" />
Text="Remove" />
</MenuFlyout> </MenuFlyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
@@ -496,40 +493,39 @@
Spacing="16"> Spacing="16">
<TextBox <TextBox
x:Name="PasteAIModelNameTextBox" x:Name="PasteAIModelNameTextBox"
x:Uid="AdvancedPaste_ModelName"
MinWidth="200" MinWidth="200"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Header="Model name" PlaceholderText="gpt-4o"
PlaceholderText="gpt-4"
Text="{x:Bind ViewModel.PasteAIProviderDraft.ModelName, Mode=TwoWay}" /> Text="{x:Bind ViewModel.PasteAIProviderDraft.ModelName, Mode=TwoWay}" />
<TextBox <TextBox
x:Name="PasteAIEndpointUrlTextBox" x:Name="PasteAIEndpointUrlTextBox"
x:Uid="AdvancedPaste_EndpointURL"
MinWidth="200" MinWidth="200"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Header="Endpoint URL"
PlaceholderText="https://your-resource.openai.azure.com/" PlaceholderText="https://your-resource.openai.azure.com/"
Text="{x:Bind ViewModel.PasteAIProviderDraft.EndpointUrl, Mode=TwoWay}" /> Text="{x:Bind ViewModel.PasteAIProviderDraft.EndpointUrl, Mode=TwoWay}" />
<PasswordBox <PasswordBox
x:Name="PasteAIApiKeyPasswordBox" x:Name="PasteAIApiKeyPasswordBox"
MinWidth="200" x:Uid="AdvancedPaste_APIKey"
Header="API key" MinWidth="200" />
PlaceholderText="Enter API Key" />
<TextBox <TextBox
x:Name="PasteAIApiVersionTextBox" x:Name="PasteAIApiVersionTextBox"
x:Uid="AdvancedPaste_APIVersion"
MinWidth="200" MinWidth="200"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Header="API version"
PlaceholderText="2024-10-01" PlaceholderText="2024-10-01"
Text="{x:Bind ViewModel.PasteAIProviderDraft.ApiVersion, Mode=TwoWay}" Text="{x:Bind ViewModel.PasteAIProviderDraft.ApiVersion, Mode=TwoWay}"
Visibility="Collapsed" /> Visibility="Collapsed" />
<TextBox <TextBox
x:Name="PasteAIDeploymentNameTextBox" x:Name="PasteAIDeploymentNameTextBox"
x:Uid="AdvancedPaste_DeploymentName"
MinWidth="200" MinWidth="200"
Header="Deployment name" PlaceholderText="gpt-4o"
PlaceholderText="gpt-4"
Text="{x:Bind ViewModel.PasteAIProviderDraft.DeploymentName, Mode=TwoWay}" /> Text="{x:Bind ViewModel.PasteAIProviderDraft.DeploymentName, Mode=TwoWay}" />
<TextBox <TextBox
x:Name="PasteAISystemPromptTextBox" x:Name="PasteAISystemPromptTextBox"
x:Uid="AdvancedPaste_SystemPrompt"
MinWidth="200" MinWidth="200"
MinHeight="76" MinHeight="76"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@@ -574,13 +570,10 @@
IsOn="{x:Bind ViewModel.PasteAIProviderDraft.EnableAdvancedAI, Mode=TwoWay}" IsOn="{x:Bind ViewModel.PasteAIProviderDraft.EnableAdvancedAI, Mode=TwoWay}"
Toggled="PasteAIEnableAdvancedAICheckBox_Toggled" Toggled="PasteAIEnableAdvancedAICheckBox_Toggled"
Visibility="Collapsed"> Visibility="Collapsed">
<ToolTipService.ToolTip>
<TextBlock Text="" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
<ToggleSwitch.Header> <ToggleSwitch.Header>
<TextBlock> <TextBlock>
<Run Text="Enable Advanced AI" /> <LineBreak /> <Run x:Uid="AdvancedPaste_EnableAdvancedAI" /> <LineBreak />
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Use built-in functions to handle complex tasks. Token consumption may increase." /> <Run x:Uid="AdvancedPaste_EnableAdvancedAIDescription" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</TextBlock> </TextBlock>
</ToggleSwitch.Header> </ToggleSwitch.Header>
</ToggleSwitch> </ToggleSwitch>

View File

@@ -644,14 +644,11 @@ Please review the placeholder content that represents the final terms and usage
<data name="AdvancedPaste_EnablePasteAIModerationToggle.Header" xml:space="preserve"> <data name="AdvancedPaste_EnablePasteAIModerationToggle.Header" xml:space="preserve">
<value>Enable OpenAI content moderation</value> <value>Enable OpenAI content moderation</value>
</data> </data>
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Header" xml:space="preserve"> <data name="AdvancedPaste_EnableAdvancedAIDescription.Text" xml:space="preserve">
<value>Enable Advanced AI</value>
</data>
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Description" xml:space="preserve">
<value>Use built-in functions to handle complex tasks. Token consumption may increase.</value> <value>Use built-in functions to handle complex tasks. Token consumption may increase.</value>
</data> </data>
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve"> <data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve">
<value>Access Clipboard History</value> <value>Show what's currently on your Clipboard and access your Clipboard history</value>
</data> </data>
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve"> <data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve">
<value>Clipboard History shows a list of previously copied items.</value> <value>Clipboard History shows a list of previously copied items.</value>
@@ -4058,11 +4055,8 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="AdvancedPaste_ShowCustomPreviewSettingsCard.Description" xml:space="preserve"> <data name="AdvancedPaste_ShowCustomPreviewSettingsCard.Description" xml:space="preserve">
<value>Preview the output of AI formats and Image to text before pasting</value> <value>Preview the output of AI formats and Image to text before pasting</value>
</data> </data>
<data name="AdvancedPaste_EnableAdvancedAI.Header" xml:space="preserve"> <data name="AdvancedPaste_EnableAdvancedAI.Text" xml:space="preserve">
<value>Advanced AI</value> <value>Enable Advanced AI</value>
</data>
<data name="AdvancedPaste_EnableAdvancedAI.Description" xml:space="preserve">
<value>Supports advanced workflows by chaining transformations and working with files and images. May use additional API credits.</value>
</data> </data>
<data name="Oobe_AdvancedPaste.Description" xml:space="preserve"> <data name="Oobe_AdvancedPaste.Description" xml:space="preserve">
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value> <value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value>
@@ -4604,7 +4598,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>If you do not have credits you will see an 'API key quota exceeded' error</value> <value>If you do not have credits you will see an 'API key quota exceeded' error</value>
</data> </data>
<data name="AdvancedPaste_CloseAfterLosingFocus.Content" xml:space="preserve"> <data name="AdvancedPaste_CloseAfterLosingFocus.Content" xml:space="preserve">
<value>Automatically close the Advanced Paste window after it loses focus</value> <value>Automatically close the window after it loses focus</value>
<comment>Advanced Paste is a product name, do not loc</comment> <comment>Advanced Paste is a product name, do not loc</comment>
</data> </data>
<data name="GPO_CommandNotFound_ForceDisabled.Title" xml:space="preserve"> <data name="GPO_CommandNotFound_ForceDisabled.Title" xml:space="preserve">
@@ -5679,4 +5673,74 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="LightSwitch_SetLocationButton.Content" xml:space="preserve"> <data name="LightSwitch_SetLocationButton.Content" xml:space="preserve">
<value>Set Location</value> <value>Set Location</value>
</data> </data>
<data name="AdvancedPaste_FL_OpenFoundryModelList.Content" xml:space="preserve">
<value>Open Foundry Local model list</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_RunFoundryLocalText.Text" xml:space="preserve">
<value>Run Foundry Local to download or add a local model</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_NoModelsDownloaded.Text" xml:space="preserve">
<value>No models downloaded</value>
</data>
<data name="AdvancedPaste_FL_LoadingStatus.Text" xml:space="preserve">
<value>Loading Foundry Local status..</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_LocalModel.Text" xml:space="preserve">
<value>Foundry Local model</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_UseCLIToDownloadModels.Text" xml:space="preserve">
<value>Use the Foundry Local CLI to download models that run locally on-device. They'll appear here.</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_RefreshModelList.Text" xml:space="preserve">
<value>Refresh model list</value>
</data>
<data name="AdvancedPaste_FL_FLNotavailableYet.Text" xml:space="preserve">
<value>Foundry Local is not available on this device yet.</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_FL_StartService.Text" xml:space="preserve">
<value>Start the Foundry Local service before returning to PowerToys.</value>
</data>
<data name="AdvancedPaste_FL_CLIGuide.Content" xml:space="preserve">
<value>Follow the Foundry Local CLI guide</value>
<comment>Do not localize "Foundry Local", it's a product name</comment>
</data>
<data name="AdvancedPaste_ModelProviders.Header" xml:space="preserve">
<value>Model providers</value>
</data>
<data name="AdvancedPaste_ModelProviders.Description" xml:space="preserve">
<value>Add online or local models</value>
</data>
<data name="AdvancedPaste_Edit.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="AdvancedPaste_Remove.Text" xml:space="preserve">
<value>Remove</value>
</data>
<data name="AdvancedPaste_ModelName.Header" xml:space="preserve">
<value>Model name</value>
</data>
<data name="AdvancedPaste_EndpointURL.Header" xml:space="preserve">
<value>Endpoint URL</value>
</data>
<data name="AdvancedPaste_APIKey.Header" xml:space="preserve">
<value>API key</value>
</data>
<data name="AdvancedPaste_APIKey.PlaceholderText" xml:space="preserve">
<value>Enter API key</value>
</data>
<data name="AdvancedPaste_APIVersion.Header" xml:space="preserve">
<value>API version</value>
</data>
<data name="AdvancedPaste_DeploymentName.Header" xml:space="preserve">
<value>Deployment name</value>
</data>
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
<value>System prompt</value>
</data>
</root> </root>

View File

@@ -172,20 +172,91 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void MigrateLegacyAIEnablement() private void MigrateLegacyAIEnablement()
{ {
if (_advancedPasteSettings.Properties.IsAIEnabled || IsOnlineAIModelsDisallowedByGPO) var properties = _advancedPasteSettings?.Properties;
if (properties is null)
{ {
return; return;
} }
if (!LegacyOpenAIKeyExists()) bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
{ bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
return;
}
_advancedPasteSettings.Properties.IsAIEnabled = true; if (IsOnlineAIModelsDisallowedByGPO)
{
if (legacyAdvancedAIConsumed)
{
SaveAndNotifySettings(); SaveAndNotifySettings();
}
return;
}
var configuration = properties.PasteAIConfiguration;
if (configuration is null)
{
configuration = new PasteAIConfiguration();
properties.PasteAIConfiguration = configuration;
}
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
{
return;
}
bool configurationUpdated = false;
if (hasLegacyProviders)
{
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
}
PasteAIProviderDefinition openAIProvider = null;
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
{
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
openAIProvider = ensureResult.Provider;
configurationUpdated |= ensureResult.Updated;
}
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
{
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
configurationUpdated = true;
}
if (legacyCredential is not null && openAIProvider is not null)
{
SavePasteAIApiKey(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
RemoveLegacyOpenAICredential();
}
bool enabledChanged = false;
if (!properties.IsAIEnabled && legacyCredential is not null)
{
properties.IsAIEnabled = true;
enabledChanged = true;
}
bool shouldPersist = configurationUpdated || enabledChanged || legacyAdvancedAIConsumed;
if (shouldPersist)
{
SaveAndNotifySettings();
if (configurationUpdated)
{
OnPropertyChanged(nameof(PasteAIConfiguration));
}
if (enabledChanged)
{
OnPropertyChanged(nameof(IsAIEnabled)); OnPropertyChanged(nameof(IsAIEnabled));
} }
}
}
public bool IsEnabled public bool IsEnabled
{ {
@@ -229,34 +300,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO; public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO;
private bool LegacyOpenAIKeyExists() private PasswordCredential TryGetLegacyOpenAICredential()
{ {
try try
{ {
PasswordVault vault = new(); PasswordVault vault = new();
var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
// return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null; credential?.RetrievePassword();
var legacyOpenAIKey = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); return credential;
if (legacyOpenAIKey != null) }
{ catch (Exception)
string credentialResource = GetAICredentialResource("OpenAI"); {
var targetProvider = PasteAIConfiguration?.ActiveProvider ?? PasteAIConfiguration?.Providers?.FirstOrDefault(); return null;
string providerId = targetProvider?.Id ?? string.Empty; }
string serviceType = targetProvider?.ServiceType ?? "OpenAI"; }
string credentialUserName = GetPasteAICredentialUserName(providerId, serviceType);
PasswordCredential cred = new(credentialResource, credentialUserName, legacyOpenAIKey.Password); private void RemoveLegacyOpenAICredential()
vault.Add(cred); {
try
// delete old key {
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); PasswordVault vault = new();
return true; TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
}
return false;
} }
catch (Exception) catch (Exception)
{ {
return false;
} }
} }
@@ -519,7 +586,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
var provider = new PasteAIProviderDefinition var provider = new PasteAIProviderDefinition
{ {
ServiceType = persistedServiceType, ServiceType = persistedServiceType,
ModelName = GetDefaultModelName(normalizedServiceType), ModelName = PasteAIProviderDefaults.GetDefaultModelName(normalizedServiceType),
EndpointUrl = string.Empty, EndpointUrl = string.Empty,
ApiVersion = string.Empty, ApiVersion = string.Empty,
DeploymentName = string.Empty, DeploymentName = string.Empty,
@@ -559,20 +626,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return serviceTypeKind; return serviceTypeKind;
} }
private static string GetDefaultModelName(AIServiceType serviceType)
{
return serviceType switch
{
AIServiceType.OpenAI => "gpt-4",
AIServiceType.AzureOpenAI => "gpt-4",
AIServiceType.Mistral => "mistral-large-latest",
AIServiceType.Google => "gemini-2.5-pro",
AIServiceType.AzureAIInference => "gpt-4o-mini",
AIServiceType.Ollama => "llama3",
_ => string.Empty,
};
}
public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType) public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType)
{ {
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType); var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
@@ -1352,7 +1405,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
} }
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>(); pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
bool configurationUpdated = AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(pasteConfig);
SubscribeToPasteAIProviders(pasteConfig); SubscribeToPasteAIProviders(pasteConfig);
if (configurationUpdated)
{
SaveAndNotifySettings();
OnPropertyChanged(nameof(PasteAIConfiguration));
}
} }
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName) private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)

View File

@@ -1000,6 +1000,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig.Enabled.CursorWrap = value; GeneralSettingsConfig.Enabled.CursorWrap = value;
OnPropertyChanged(nameof(IsCursorWrapEnabled)); OnPropertyChanged(nameof(IsCursorWrapEnabled));
// Auto-enable the AutoActivate setting when CursorWrap is enabled
// This ensures cursor wrapping is active immediately after enabling
if (value && !_cursorWrapAutoActivate)
{
CursorWrapAutoActivate = true;
}
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString()); SendConfigMSG(outgoing.ToString());

View File

@@ -24,6 +24,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
public class ZoomItViewModel : Observable public class ZoomItViewModel : Observable
{ {
private const string FormatGif = "GIF";
private const string FormatMp4 = "MP4";
private ISettingsUtils SettingsUtils { get; set; } private ISettingsUtils SettingsUtils { get; set; }
private GeneralSettings GeneralSettingsConfig { get; set; } private GeneralSettings GeneralSettingsConfig { get; set; }
@@ -656,12 +659,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
get get
{ {
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF") if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
{ {
return 0; return 0;
} }
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4") if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
{ {
return 1; return 1;
} }
@@ -672,19 +675,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
set set
{ {
int format = 0; int format = 0;
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF") if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
{ {
format = 0; format = 0;
} }
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4") if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
{ {
format = 1; format = 1;
} }
if (format != value) if (format != value)
{ {
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4"; _zoomItSettings.Properties.RecordFormat.Value = value == 0 ? FormatGif : FormatMp4;
OnPropertyChanged(nameof(RecordFormatIndex)); OnPropertyChanged(nameof(RecordFormatIndex));
NotifySettingsChanged(); NotifySettingsChanged();