mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01:00
Merge branch 'main' into shawn/fixAPPrompttextbox
This commit is contained in:
40
README.md
40
README.md
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 |
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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="" />
|
<FontIcon
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
FontSize="24"
|
||||||
|
Glyph="" />
|
||||||
<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="" />
|
<FontIcon FontSize="16" Glyph="" />
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -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=}"
|
Icon="{ui:FontIcon Glyph=}"
|
||||||
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=}"
|
Icon="{ui:FontIcon Glyph=}"
|
||||||
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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user