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);
}
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 bool LegacyOpenAIKeyExists() 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

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
--> -->
@@ -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,19 +172,90 @@ 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;
if (IsOnlineAIModelsDisallowedByGPO)
{
if (legacyAdvancedAIConsumed)
{
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; return;
} }
_advancedPasteSettings.Properties.IsAIEnabled = true; bool configurationUpdated = false;
SaveAndNotifySettings();
OnPropertyChanged(nameof(IsAIEnabled)); 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));
}
}
} }
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();