mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-20 01:59:50 +01:00
Compare commits
33 Commits
async-cpp-
...
vanzue/hot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ab3823609 | ||
|
|
5094d296cd | ||
|
|
4e31b88fdb | ||
|
|
b62c5da13b | ||
|
|
068cccc22b | ||
|
|
1f0603fb2b | ||
|
|
374dccc475 | ||
|
|
64113a1ca9 | ||
|
|
d858dcc1bb | ||
|
|
3539041b72 | ||
|
|
d9b1ca0fd8 | ||
|
|
883ec9c815 | ||
|
|
4b1f54fff9 | ||
|
|
d130f3596d | ||
|
|
fdd0832eb2 | ||
|
|
fcff4bc056 | ||
|
|
cb542079ff | ||
|
|
ea94bcdd6e | ||
|
|
cd467785f2 | ||
|
|
b79b7f7bf6 | ||
|
|
6ab7e878eb | ||
|
|
064484c77c | ||
|
|
60c886f817 | ||
|
|
db41c5a65c | ||
|
|
8f87058508 | ||
|
|
755c138723 | ||
|
|
8b066cea2e | ||
|
|
2d92ccdf3b | ||
|
|
83ea0c2f28 | ||
|
|
cb81a99c5f | ||
|
|
48a3f4fa87 | ||
|
|
6c05e44680 | ||
|
|
6505cd7a63 |
@@ -291,6 +291,7 @@
|
|||||||
"Mono.Cecil.Rocks.dll",
|
"Mono.Cecil.Rocks.dll",
|
||||||
"Newtonsoft.Json.dll",
|
"Newtonsoft.Json.dll",
|
||||||
"CommunityToolkit.WinUI.Controls.TitleBar.dll",
|
"CommunityToolkit.WinUI.Controls.TitleBar.dll",
|
||||||
|
"CommunityToolkit.WinUI.Controls.OpacityMaskView.dll",
|
||||||
|
|
||||||
"NLog.dll",
|
"NLog.dll",
|
||||||
"HtmlAgilityPack.dll",
|
"HtmlAgilityPack.dll",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
||||||
<PackageVersion Include="AdaptiveCards.Rendering.WinUI3" Version="2.1.0-beta" />
|
<PackageVersion Include="AdaptiveCards.Rendering.WinUI3" Version="2.1.0-beta" />
|
||||||
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.5" />
|
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.5" />
|
||||||
|
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251101-build.2372" />
|
||||||
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
||||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||||
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||||
|
|||||||
@@ -1498,6 +1498,7 @@ SOFTWARE.
|
|||||||
- CoenM.ImageSharp.ImageHash
|
- CoenM.ImageSharp.ImageHash
|
||||||
- CommunityToolkit.Common
|
- CommunityToolkit.Common
|
||||||
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
|
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
|
||||||
|
- CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView
|
||||||
- CommunityToolkit.Mvvm
|
- CommunityToolkit.Mvvm
|
||||||
- CommunityToolkit.WinUI.Animations
|
- CommunityToolkit.WinUI.Animations
|
||||||
- CommunityToolkit.WinUI.Collections
|
- CommunityToolkit.WinUI.Collections
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <ProjectTelemetry.h>
|
#include <ProjectTelemetry.h>
|
||||||
#include <spdlog/sinks/base_sink.h>
|
#include <spdlog/sinks/base_sink.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "../../src/common/logger/logger.h"
|
#include "../../src/common/logger/logger.h"
|
||||||
#include "../../src/common/utils/gpo.h"
|
#include "../../src/common/utils/gpo.h"
|
||||||
@@ -856,14 +857,69 @@ UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
winrt::Windows::Security::Credentials::PasswordVault vault;
|
|
||||||
winrt::Windows::Security::Credentials::PasswordCredential cred;
|
|
||||||
|
|
||||||
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
|
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
|
||||||
ExitOnFailure(hr, "Failed to initialize");
|
ExitOnFailure(hr, "Failed to initialize");
|
||||||
|
|
||||||
cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey");
|
winrt::Windows::Security::Credentials::PasswordVault vault;
|
||||||
vault.Remove(cred);
|
|
||||||
|
auto hasPrefix = [](std::wstring_view value, wchar_t const* prefix) {
|
||||||
|
std::wstring_view prefixView{ prefix };
|
||||||
|
return value.compare(0, prefixView.size(), prefixView) == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wchar_t* resourcePrefixes[] = {
|
||||||
|
L"https://platform.openai.com/api-keys",
|
||||||
|
L"https://azure.microsoft.com/products/ai-services/openai-service",
|
||||||
|
L"https://azure.microsoft.com/products/ai-services/ai-inference",
|
||||||
|
L"https://console.mistral.ai/account/api-keys",
|
||||||
|
L"https://ai.google.dev/",
|
||||||
|
};
|
||||||
|
|
||||||
|
const wchar_t* usernamePrefixes[] = {
|
||||||
|
L"PowerToys_AdvancedPaste_",
|
||||||
|
};
|
||||||
|
|
||||||
|
auto credentials = vault.RetrieveAll();
|
||||||
|
for (auto const& credential : credentials)
|
||||||
|
{
|
||||||
|
bool shouldRemove = false;
|
||||||
|
|
||||||
|
std::wstring resource{ credential.Resource() };
|
||||||
|
for (auto const prefix : resourcePrefixes)
|
||||||
|
{
|
||||||
|
if (hasPrefix(resource, prefix))
|
||||||
|
{
|
||||||
|
shouldRemove = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldRemove)
|
||||||
|
{
|
||||||
|
std::wstring username{ credential.UserName() };
|
||||||
|
for (auto const prefix : usernamePrefixes)
|
||||||
|
{
|
||||||
|
if (hasPrefix(username, prefix))
|
||||||
|
{
|
||||||
|
shouldRemove = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldRemove)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
vault.Remove(credential);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
Description="PowerToys OCR Module"
|
Description="PowerToys OCR Module"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Images\Square150x150Logo.png"
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
Square44x44Logo="Images\Square44x44Logo.png">
|
Square44x44Logo="Images\Square44x44Logo.png"
|
||||||
|
AppListEntry="none">
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
</Application>
|
</Application>
|
||||||
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
<Application Id="PowerToys.SettingsUI" Executable="WinUI3Apps\PowerToys.Settings.exe" EntryPoint="Windows.FullTrustApplication">
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
Description="PowerToys Settings UI"
|
Description="PowerToys Settings UI"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Images\Square150x150Logo.png"
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
Square44x44Logo="Images\Square44x44Logo.png">
|
Square44x44Logo="Images\Square44x44Logo.png"
|
||||||
|
AppListEntry="none">
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
</Application>
|
</Application>
|
||||||
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
<Application Id="PowerToys.ImageResizerUI" Executable="WinUI3Apps\PowerToys.ImageResizer.exe" EntryPoint="Windows.FullTrustApplication">
|
||||||
@@ -60,7 +62,8 @@
|
|||||||
Description="PowerToys Image Resizer UI"
|
Description="PowerToys Image Resizer UI"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Images\Square150x150Logo.png"
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
Square44x44Logo="Images\Square44x44Logo.png">
|
Square44x44Logo="Images\Square44x44Logo.png"
|
||||||
|
AppListEntry="none">
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
</Application>
|
</Application>
|
||||||
</Applications>
|
</Applications>
|
||||||
|
|||||||
@@ -10,6 +10,23 @@ namespace LanguageModelProvider.FoundryLocal;
|
|||||||
internal sealed class FoundryClient
|
internal sealed class FoundryClient
|
||||||
{
|
{
|
||||||
public static async Task<FoundryClient?> CreateAsync()
|
public static async Task<FoundryClient?> CreateAsync()
|
||||||
|
{
|
||||||
|
// First attempt with current environment
|
||||||
|
var client = await TryCreateClientAsync().ConfigureAwait(false);
|
||||||
|
if (client != null)
|
||||||
|
{
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If failed, refresh PATH from registry and retry once
|
||||||
|
// This handles cases where PowerToys was launched by MSI installer.
|
||||||
|
Logger.LogInfo("[FoundryClient] First attempt failed, refreshing PATH and retrying");
|
||||||
|
RefreshEnvironmentPath();
|
||||||
|
|
||||||
|
return await TryCreateClientAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<FoundryClient?> TryCreateClientAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -168,41 +185,95 @@ internal sealed class FoundryClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> EnsureModelLoaded(string modelId)
|
public async Task<bool> EnsureModelLoaded(string modelId)
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"[FoundryClient] EnsureModelLoaded called with: {modelId}");
|
||||||
|
|
||||||
|
// Check if already loaded
|
||||||
|
if (await IsModelLoaded(modelId).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
Logger.LogInfo($"[FoundryClient] Model already loaded: {modelId}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the model
|
||||||
|
Logger.LogInfo($"[FoundryClient] Loading model: {modelId}");
|
||||||
|
await _foundryManager.LoadModelAsync(modelId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Verify it's loaded
|
||||||
|
var loaded = await IsModelLoaded(modelId).ConfigureAwait(false);
|
||||||
|
Logger.LogInfo($"[FoundryClient] Model load result: {loaded}");
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EnsureRunning()
|
||||||
|
{
|
||||||
|
if (!_foundryManager.IsServiceRunning)
|
||||||
|
{
|
||||||
|
await _foundryManager.StartServiceAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the PATH environment variable from the system registry.
|
||||||
|
/// This is necessary when tools are installed while PowerToys is running,
|
||||||
|
/// as the installer updates the system PATH but running processes don't see the change.
|
||||||
|
/// </summary>
|
||||||
|
private static void RefreshEnvironmentPath()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInfo($"[FoundryClient] EnsureModelLoaded called with: {modelId}");
|
Logger.LogInfo("[FoundryClient] Refreshing PATH environment variable from system");
|
||||||
|
|
||||||
// Check if already loaded
|
var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) ?? string.Empty;
|
||||||
if (await IsModelLoaded(modelId).ConfigureAwait(false))
|
var machinePath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty;
|
||||||
|
var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty;
|
||||||
|
|
||||||
|
var pathsToAdd = new List<string>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(currentPath))
|
||||||
{
|
{
|
||||||
Logger.LogInfo($"[FoundryClient] Model already loaded: {modelId}");
|
pathsToAdd.AddRange(currentPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if model exists in cache
|
if (!string.IsNullOrWhiteSpace(userPath))
|
||||||
var cachedModels = await ListCachedModels().ConfigureAwait(false);
|
|
||||||
Logger.LogInfo($"[FoundryClient] Cached models: {string.Join(", ", cachedModels.Select(m => m.Name))}");
|
|
||||||
|
|
||||||
if (!cachedModels.Any(m => m.Name == modelId))
|
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"[FoundryClient] Model not found in cache: {modelId}");
|
var userPaths = userPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return false;
|
foreach (var path in userPaths)
|
||||||
|
{
|
||||||
|
if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
pathsToAdd.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the model
|
if (!string.IsNullOrWhiteSpace(machinePath))
|
||||||
Logger.LogInfo($"[FoundryClient] Loading model: {modelId}");
|
{
|
||||||
await _foundryManager.LoadModelAsync(modelId).ConfigureAwait(false);
|
var machinePaths = machinePath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var path in machinePaths)
|
||||||
|
{
|
||||||
|
if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
pathsToAdd.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify it's loaded
|
var newPath = string.Join(Path.PathSeparator.ToString(), pathsToAdd);
|
||||||
var loaded = await IsModelLoaded(modelId).ConfigureAwait(false);
|
|
||||||
Logger.LogInfo($"[FoundryClient] Model load result: {loaded}");
|
if (currentPath != newPath)
|
||||||
return loaded;
|
{
|
||||||
|
Logger.LogInfo("[FoundryClient] Updating process PATH with latest system values");
|
||||||
|
Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInfo("[FoundryClient] PATH is already up to date");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError($"[FoundryClient] EnsureModelLoaded exception: {ex.Message}");
|
Logger.LogError($"[FoundryClient] Failed to refresh PATH: {ex.Message}");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace LanguageModelProvider;
|
|||||||
|
|
||||||
public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||||
{
|
{
|
||||||
private IEnumerable<ModelDetails>? _downloadedModels;
|
private FoundryClient? _foundryClient;
|
||||||
private FoundryClient? _foundryManager;
|
private IEnumerable<FoundryCatalogModel>? _catalogModels;
|
||||||
private string? _serviceUrl;
|
private string? _serviceUrl;
|
||||||
|
|
||||||
public static FoundryLocalModelProvider Instance { get; } = new();
|
public static FoundryLocalModelProvider Instance { get; } = new();
|
||||||
@@ -22,70 +22,49 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
|||||||
|
|
||||||
public string ProviderDescription => "The model will run locally via Foundry Local";
|
public string ProviderDescription => "The model will run locally via Foundry Local";
|
||||||
|
|
||||||
public string UrlPrefix => "fl://";
|
public IChatClient? GetIChatClient(string modelId)
|
||||||
|
|
||||||
public IChatClient? GetIChatClient(string url)
|
|
||||||
{
|
{
|
||||||
try
|
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}");
|
||||||
{
|
InitializeAsync().GetAwaiter().GetResult();
|
||||||
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {url}");
|
|
||||||
InitializeAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError($"[FoundryLocal] Failed to initialize: {ex.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryManager == null)
|
|
||||||
{
|
|
||||||
Logger.LogError("[FoundryLocal] Service URL or manager is null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract model ID from URL (format: fl://modelname)
|
|
||||||
var modelId = url.Replace(UrlPrefix, string.Empty).Trim('/');
|
|
||||||
if (string.IsNullOrWhiteSpace(modelId))
|
if (string.IsNullOrWhiteSpace(modelId))
|
||||||
{
|
{
|
||||||
Logger.LogError("[FoundryLocal] Model ID is empty after extraction");
|
Logger.LogError("[FoundryLocal] Model ID is empty after extraction");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInfo($"[FoundryLocal] Extracted model ID: {modelId}");
|
// Check if model is in catalog
|
||||||
|
var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false;
|
||||||
|
if (!isInCatalog)
|
||||||
|
{
|
||||||
|
var errorMessage = $"{modelId} is not supported in Foundry Local. Please configure supported models in Settings.";
|
||||||
|
Logger.LogError($"[FoundryLocal] {errorMessage}");
|
||||||
|
throw new InvalidOperationException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the model is loaded before returning chat client
|
// Ensure the model is loaded before returning chat client
|
||||||
try
|
var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||||
|
if (!isLoaded)
|
||||||
{
|
{
|
||||||
var isLoaded = _foundryManager.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
|
||||||
if (!isLoaded)
|
throw new InvalidOperationException($"Failed to load the model '{modelId}'.");
|
||||||
{
|
|
||||||
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.LogInfo($"[FoundryLocal] Model is loaded: {modelId}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError($"[FoundryLocal] Exception ensuring model loaded: {ex.Message}");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
|
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
|
||||||
var baseUri = _foundryManager.GetServiceUri();
|
var baseUri = _foundryClient.GetServiceUri();
|
||||||
if (baseUri == null)
|
if (baseUri == null)
|
||||||
{
|
{
|
||||||
Logger.LogError("[FoundryLocal] Service URI is null");
|
const string message = "Foundry Local service URL is not available. Please make sure Foundry Local is installed and running.";
|
||||||
return null;
|
Logger.LogError($"[FoundryLocal] {message}");
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var endpointUri = new Uri($"{baseUri.ToString().TrimEnd('/')}/v1");
|
var endpointUri = new Uri($"{baseUri.ToString().TrimEnd('/')}/v1");
|
||||||
Logger.LogInfo($"[FoundryLocal] Creating OpenAI client with endpoint: {endpointUri}");
|
Logger.LogInfo($"[FoundryLocal] Creating OpenAI client with endpoint: {endpointUri}");
|
||||||
Logger.LogInfo($"[FoundryLocal] Model ID for chat client: {modelId}");
|
|
||||||
|
|
||||||
return new OpenAIClient(
|
return new OpenAIClient(
|
||||||
new ApiKeyCredential("none"),
|
new ApiKeyCredential("none"),
|
||||||
new OpenAIClientOptions { Endpoint = endpointUri })
|
new OpenAIClientOptions { Endpoint = endpointUri, NetworkTimeout = TimeSpan.FromMinutes(5) })
|
||||||
.GetChatClient(modelId)
|
.GetChatClient(modelId)
|
||||||
.AsIChatClient();
|
.AsIChatClient();
|
||||||
}
|
}
|
||||||
@@ -111,48 +90,16 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
|||||||
return $"new OpenAIClient(new ApiKeyCredential(\"none\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{_serviceUrl}/v1\") }}).GetChatClient(\"{modelId}\").AsIChatClient()";
|
return $"new OpenAIClient(new ApiKeyCredential(\"none\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{_serviceUrl}/v1\") }}).GetChatClient(\"{modelId}\").AsIChatClient()";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default)
|
public async Task<IEnumerable<ModelDetails>> GetModelsAsync(CancellationToken cancelationToken = default)
|
||||||
{
|
{
|
||||||
if (ignoreCached)
|
|
||||||
{
|
|
||||||
Logger.LogInfo("[FoundryLocal] Ignoring cached models, resetting");
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
await InitializeAsync(cancelationToken);
|
await InitializeAsync(cancelationToken);
|
||||||
|
|
||||||
Logger.LogInfo($"[FoundryLocal] Returning {_downloadedModels?.Count() ?? 0} downloaded models");
|
if (_foundryClient == null)
|
||||||
return _downloadedModels ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reset()
|
|
||||||
{
|
|
||||||
_downloadedModels = null;
|
|
||||||
_ = InitializeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeAsync(CancellationToken cancelationToken = default)
|
|
||||||
{
|
|
||||||
if (_foundryManager != null && _downloadedModels != null && _downloadedModels.Any())
|
|
||||||
{
|
{
|
||||||
return;
|
return Array.Empty<ModelDetails>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInfo("[FoundryLocal] Initializing provider");
|
var cachedModels = await _foundryClient.ListCachedModels();
|
||||||
_foundryManager ??= await FoundryClient.CreateAsync();
|
|
||||||
|
|
||||||
if (_foundryManager == null)
|
|
||||||
{
|
|
||||||
Logger.LogError("[FoundryLocal] Failed to create Foundry client");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_serviceUrl ??= await _foundryManager.GetServiceUrl();
|
|
||||||
Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
|
|
||||||
|
|
||||||
var cachedModels = await _foundryManager.ListCachedModels();
|
|
||||||
Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models");
|
|
||||||
|
|
||||||
List<ModelDetails> downloadedModels = [];
|
List<ModelDetails> downloadedModels = [];
|
||||||
|
|
||||||
foreach (var model in cachedModels)
|
foreach (var model in cachedModels)
|
||||||
@@ -162,23 +109,47 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
|||||||
{
|
{
|
||||||
Id = $"fl-{model.Name}",
|
Id = $"fl-{model.Name}",
|
||||||
Name = model.Name,
|
Name = model.Name,
|
||||||
Url = $"{UrlPrefix}{model.Name}",
|
Url = $"fl://{model.Name}",
|
||||||
Description = $"{model.Name} running locally with Foundry Local",
|
Description = $"{model.Name} running locally with Foundry Local",
|
||||||
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
||||||
SupportedOnQualcomm = true,
|
|
||||||
ProviderModelDetails = model,
|
ProviderModelDetails = model,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadedModels = downloadedModels;
|
return downloadedModels;
|
||||||
Logger.LogInfo($"[FoundryLocal] Initialization complete. Total downloaded models: {downloadedModels.Count}");
|
}
|
||||||
|
|
||||||
|
private async Task InitializeAsync(CancellationToken cancelationToken = default)
|
||||||
|
{
|
||||||
|
if (_foundryClient != null && _catalogModels != null && _catalogModels.Any())
|
||||||
|
{
|
||||||
|
await _foundryClient.EnsureRunning().ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("[FoundryLocal] Initializing provider");
|
||||||
|
_foundryClient ??= await FoundryClient.CreateAsync();
|
||||||
|
|
||||||
|
if (_foundryClient == null)
|
||||||
|
{
|
||||||
|
const string message = "Foundry Local client could not be created. Please make sure Foundry Local is installed and running.";
|
||||||
|
Logger.LogError($"[FoundryLocal] {message}");
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_serviceUrl ??= await _foundryClient.GetServiceUrl();
|
||||||
|
Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
|
||||||
|
|
||||||
|
var catalogModels = await _foundryClient.ListCatalogModels();
|
||||||
|
Logger.LogInfo($"[FoundryLocal] Found {catalogModels.Count} catalog models");
|
||||||
|
_catalogModels = catalogModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsAvailable()
|
public async Task<bool> IsAvailable()
|
||||||
{
|
{
|
||||||
Logger.LogInfo("[FoundryLocal] Checking availability");
|
Logger.LogInfo("[FoundryLocal] Checking availability");
|
||||||
await InitializeAsync();
|
await InitializeAsync();
|
||||||
var available = _foundryManager != null;
|
var available = _foundryClient != null;
|
||||||
Logger.LogInfo($"[FoundryLocal] Available: {available}");
|
Logger.LogInfo($"[FoundryLocal] Available: {available}");
|
||||||
return available;
|
return available;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,11 @@ public interface ILanguageModelProvider
|
|||||||
{
|
{
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
|
||||||
string UrlPrefix { get; }
|
|
||||||
|
|
||||||
string ProviderDescription { get; }
|
string ProviderDescription { get; }
|
||||||
|
|
||||||
Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default);
|
Task<IEnumerable<ModelDetails>> GetModelsAsync(CancellationToken cancelationToken = default);
|
||||||
|
|
||||||
IChatClient? GetIChatClient(string url);
|
IChatClient? GetIChatClient(string modelId);
|
||||||
|
|
||||||
string GetIChatClientString(string url);
|
string GetIChatClientString(string url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
// 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.Collections.Concurrent;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
|
|
||||||
namespace LanguageModelProvider;
|
|
||||||
|
|
||||||
public sealed class LanguageModelService
|
|
||||||
{
|
|
||||||
private readonly ConcurrentDictionary<string, ILanguageModelProvider> _providersByPrefix;
|
|
||||||
|
|
||||||
public LanguageModelService(IEnumerable<ILanguageModelProvider> providers)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(providers);
|
|
||||||
|
|
||||||
_providersByPrefix = new ConcurrentDictionary<string, ILanguageModelProvider>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
foreach (var provider in providers)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(provider.UrlPrefix))
|
|
||||||
{
|
|
||||||
_providersByPrefix[provider.UrlPrefix] = provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LanguageModelService CreateDefault()
|
|
||||||
{
|
|
||||||
return new LanguageModelService(new[]
|
|
||||||
{
|
|
||||||
FoundryLocalModelProvider.Instance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<ILanguageModelProvider> Providers => _providersByPrefix.Values.ToArray();
|
|
||||||
|
|
||||||
public bool RegisterProvider(ILanguageModelProvider provider)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(provider);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(provider.UrlPrefix))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Provider must supply a URL prefix.", nameof(provider));
|
|
||||||
}
|
|
||||||
|
|
||||||
_providersByPrefix[provider.UrlPrefix] = provider;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILanguageModelProvider? GetProviderFor(string? modelReference)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(modelReference))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var provider in _providersByPrefix.Values)
|
|
||||||
{
|
|
||||||
if (modelReference.StartsWith(provider.UrlPrefix, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IReadOnlyList<ModelDetails>> GetModelsAsync(bool refresh = false, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
List<ModelDetails> models = [];
|
|
||||||
|
|
||||||
foreach (var provider in _providersByPrefix.Values)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var providerModels = await provider.GetModelsAsync(refresh, cancellationToken).ConfigureAwait(false);
|
|
||||||
models.AddRange(providerModels);
|
|
||||||
}
|
|
||||||
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IChatClient? GetClient(ModelDetails model)
|
|
||||||
{
|
|
||||||
if (model is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reference = !string.IsNullOrWhiteSpace(model.Url) ? model.Url : model.Id;
|
|
||||||
return GetClient(reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IChatClient? GetClient(string? modelReference)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(modelReference))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = GetProviderFor(modelReference);
|
|
||||||
|
|
||||||
return provider?.GetIChatClient(modelReference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,8 +24,6 @@ public class ModelDetails
|
|||||||
|
|
||||||
public List<HardwareAccelerator> HardwareAccelerators { get; set; } = [];
|
public List<HardwareAccelerator> HardwareAccelerators { get; set; } = [];
|
||||||
|
|
||||||
public bool SupportedOnQualcomm { get; set; }
|
|
||||||
|
|
||||||
public string License { get; set; } = string.Empty;
|
public string License { get; set; } = string.Empty;
|
||||||
|
|
||||||
public object? ProviderModelDetails { get; set; }
|
public object? ProviderModelDetails { get; set; }
|
||||||
|
|||||||
@@ -558,7 +558,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="AIProvidersFlyoutHeader"
|
x:Uid="AIProvidersFlyoutHeader"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
<ListView
|
<ListView
|
||||||
x:Name="AIProviderListView"
|
x:Name="AIProviderListView"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
|||||||
@@ -264,7 +264,8 @@
|
|||||||
<Button
|
<Button
|
||||||
Padding="0"
|
Padding="0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource SubtleButtonStyle}">
|
Style="{StaticResource SubtleButtonStyle}"
|
||||||
|
Visibility="{x:Bind ViewModel.HasLegalLinks, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
<FontIcon FontSize="12" Glyph="" />
|
<FontIcon FontSize="12" Glyph="" />
|
||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<Flyout>
|
<Flyout>
|
||||||
@@ -298,47 +299,49 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</controls:PromptBox.Footer>
|
</controls:PromptBox.Footer>
|
||||||
</controls:PromptBox>
|
</controls:PromptBox>
|
||||||
<Grid Grid.Row="2" RowSpacing="4">
|
<ScrollViewer Grid.Row="2">
|
||||||
<Grid.RowDefinitions>
|
<Grid RowSpacing="4">
|
||||||
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="{x:Bind ViewModel.StandardPasteFormats.Count, Mode=OneWay, Converter={StaticResource standardPasteFormatsToHeightConverter}}" />
|
||||||
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="*" MinHeight="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource customActionsToMinHeightConverter}}" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
<RowDefinition Height="Auto" />
|
||||||
<ListView
|
</Grid.RowDefinitions>
|
||||||
x:Name="PasteOptionsListView"
|
<ListView
|
||||||
Grid.Row="0"
|
x:Name="PasteOptionsListView"
|
||||||
VerticalAlignment="Bottom"
|
Grid.Row="0"
|
||||||
IsItemClickEnabled="True"
|
VerticalAlignment="Bottom"
|
||||||
ItemClick="PasteFormat_ItemClick"
|
IsItemClickEnabled="True"
|
||||||
ItemContainerTransitions="{x:Null}"
|
ItemClick="PasteFormat_ItemClick"
|
||||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
ItemContainerTransitions="{x:Null}"
|
||||||
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
|
||||||
ScrollViewer.VerticalScrollMode="Auto"
|
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||||
SelectionMode="None"
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
TabIndex="1" />
|
SelectionMode="None"
|
||||||
<Rectangle
|
TabIndex="1" />
|
||||||
Grid.Row="1"
|
<Rectangle
|
||||||
Height="1"
|
Grid.Row="1"
|
||||||
HorizontalAlignment="Stretch"
|
Height="1"
|
||||||
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
|
HorizontalAlignment="Stretch"
|
||||||
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
Fill="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||||
|
Visibility="{x:Bind ViewModel.CustomActionPasteFormats.Count, Mode=OneWay, Converter={StaticResource countToVisibilityConverter}}" />
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
x:Name="CustomActionsListView"
|
x:Name="CustomActionsListView"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
IsItemClickEnabled="True"
|
IsItemClickEnabled="True"
|
||||||
ItemClick="PasteFormat_ItemClick"
|
ItemClick="PasteFormat_ItemClick"
|
||||||
ItemContainerTransitions="{x:Null}"
|
ItemContainerTransitions="{x:Null}"
|
||||||
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
|
||||||
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||||
ScrollViewer.VerticalScrollMode="Auto"
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
SelectionMode="None"
|
SelectionMode="None"
|
||||||
TabIndex="2" />
|
TabIndex="2" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ namespace AdvancedPaste.Settings
|
|||||||
}
|
}
|
||||||
|
|
||||||
var properties = settings.Properties;
|
var properties = settings.Properties;
|
||||||
|
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||||
|
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||||
|
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||||
|
|
||||||
|
if (legacyCredential is null)
|
||||||
|
{
|
||||||
|
return legacyAdvancedAIConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
var configuration = properties.PasteAIConfiguration;
|
var configuration = properties.PasteAIConfiguration;
|
||||||
|
|
||||||
if (configuration is null)
|
if (configuration is null)
|
||||||
@@ -176,30 +185,11 @@ namespace AdvancedPaste.Settings
|
|||||||
properties.PasteAIConfiguration = configuration;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool configurationUpdated = false;
|
bool configurationUpdated = false;
|
||||||
|
|
||||||
if (hasLegacyProviders)
|
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||||
{
|
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
configurationUpdated |= ensureResult.Updated;
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||||
{
|
{
|
||||||
@@ -207,16 +197,17 @@ namespace AdvancedPaste.Settings
|
|||||||
configurationUpdated = true;
|
configurationUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (legacyCredential is not null && openAIProvider is not null)
|
if (openAIProvider is not null)
|
||||||
{
|
{
|
||||||
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||||
RemoveLegacyOpenAICredential();
|
RemoveLegacyOpenAICredential();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool shouldEnableAI = true;
|
||||||
bool enabledUpdated = false;
|
bool enabledUpdated = false;
|
||||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
if (properties.IsAIEnabled != shouldEnableAI)
|
||||||
{
|
{
|
||||||
properties.IsAIEnabled = true;
|
properties.IsAIEnabled = shouldEnableAI;
|
||||||
enabledUpdated = true;
|
enabledUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,6 @@ public sealed class AdvancedAIKernelService : KernelServiceBase
|
|||||||
return new OpenAIPromptExecutionSettings
|
return new OpenAIPromptExecutionSettings
|
||||||
{
|
{
|
||||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
|
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
|
||||||
Temperature = 0.01,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using AdvancedPaste.Helpers;
|
||||||
using AdvancedPaste.Models;
|
using AdvancedPaste.Models;
|
||||||
using LanguageModelProvider;
|
using LanguageModelProvider;
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
@@ -23,7 +24,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
|||||||
|
|
||||||
public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new FoundryLocalPasteProvider(config));
|
public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new FoundryLocalPasteProvider(config));
|
||||||
|
|
||||||
private static readonly LanguageModelService LanguageModels = LanguageModelService.CreateDefault();
|
private static readonly FoundryLocalModelProvider _modelProvider = FoundryLocalModelProvider.Instance;
|
||||||
|
|
||||||
private readonly PasteAIConfig _config;
|
private readonly PasteAIConfig _config;
|
||||||
|
|
||||||
@@ -33,10 +34,6 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
|||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ProviderName => AIServiceType.FoundryLocal.ToNormalizedKey();
|
|
||||||
|
|
||||||
public string DisplayName => string.IsNullOrWhiteSpace(_config?.Model) ? "Foundry Local" : _config.Model;
|
|
||||||
|
|
||||||
public async Task<bool> IsAvailableAsync(CancellationToken cancellationToken)
|
public async Task<bool> IsAvailableAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
@@ -72,21 +69,25 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
|||||||
throw new PasteActionException(
|
throw new PasteActionException(
|
||||||
"No Foundry Local model selected",
|
"No Foundry Local model selected",
|
||||||
new InvalidOperationException("Model identifier is required"),
|
new InvalidOperationException("Model identifier is required"),
|
||||||
aiServiceMessage: "Please select a model in the AI provider settings. Model identifier should be in the format 'fl://model-name'.");
|
aiServiceMessage: "Please select a model in the AI provider settings.");
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var chatClient = LanguageModels.GetClient(modelReference);
|
|
||||||
if (chatClient is null)
|
|
||||||
{
|
|
||||||
throw new PasteActionException(
|
|
||||||
$"Unable to load Foundry Local model: {modelReference}",
|
|
||||||
new InvalidOperationException("Chat client resolution failed"),
|
|
||||||
aiServiceMessage: "The model may not be downloaded or the Foundry Local service may not be running. Please check the model status in settings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract actual model ID from the URL (format: fl://modelId)
|
IChatClient chatClient;
|
||||||
var actualModelId = modelReference.Replace("fl://", string.Empty).Trim('/');
|
try
|
||||||
|
{
|
||||||
|
chatClient = _modelProvider.GetIChatClient(modelReference);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
// GetIChatClient throws InvalidOperationException for user-facing errors
|
||||||
|
var errorMessage = string.Format(System.Globalization.CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("FoundryLocal_UnableToLoadModel"), modelReference);
|
||||||
|
throw new PasteActionException(
|
||||||
|
errorMessage,
|
||||||
|
ex,
|
||||||
|
aiServiceMessage: ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
var userMessageContent = $"""
|
var userMessageContent = $"""
|
||||||
User instructions:
|
User instructions:
|
||||||
@@ -104,7 +105,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
|||||||
new(ChatRole.User, userMessageContent),
|
new(ChatRole.User, userMessageContent),
|
||||||
};
|
};
|
||||||
|
|
||||||
var chatOptions = CreateChatOptions(_config?.SystemPrompt, actualModelId);
|
var chatOptions = CreateChatOptions(_config?.SystemPrompt, modelReference);
|
||||||
|
|
||||||
progress?.Report(0.1);
|
progress?.Report(0.1);
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
|||||||
var options = new ChatOptions
|
var options = new ChatOptions
|
||||||
{
|
{
|
||||||
ModelId = modelReference,
|
ModelId = modelReference,
|
||||||
|
MaxOutputTokens = 2048,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(systemPrompt))
|
if (!string.IsNullOrWhiteSpace(systemPrompt))
|
||||||
|
|||||||
@@ -157,8 +157,6 @@ namespace AdvancedPaste.Services.CustomActions
|
|||||||
{
|
{
|
||||||
AIServiceType.OpenAI or AIServiceType.AzureOpenAI => new OpenAIPromptExecutionSettings
|
AIServiceType.OpenAI or AIServiceType.AzureOpenAI => new OpenAIPromptExecutionSettings
|
||||||
{
|
{
|
||||||
Temperature = 0.01,
|
|
||||||
MaxTokens = 2000,
|
|
||||||
FunctionChoiceBehavior = null,
|
FunctionChoiceBehavior = null,
|
||||||
},
|
},
|
||||||
_ => new PromptExecutionSettings(),
|
_ => new PromptExecutionSettings(),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public abstract class KernelServiceBase(
|
|||||||
ICustomActionTransformService customActionTransformService) : IKernelService
|
ICustomActionTransformService customActionTransformService) : IKernelService
|
||||||
{
|
{
|
||||||
private const string PromptParameterName = "prompt";
|
private const string PromptParameterName = "prompt";
|
||||||
|
private const string DefaultSystemPrompt = "You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task. Call function when necessary to help user finish the transformation task. You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best. The user will put in a request to format their clipboard data and you will fulfill it. Do not output anything else besides the reformatted clipboard content.";
|
||||||
|
|
||||||
private readonly IKernelQueryCacheService _queryCacheService = queryCacheService;
|
private readonly IKernelQueryCacheService _queryCacheService = queryCacheService;
|
||||||
private readonly IPromptModerationService _promptModerationService = promptModerationService;
|
private readonly IPromptModerationService _promptModerationService = promptModerationService;
|
||||||
@@ -144,7 +145,8 @@ public abstract class KernelServiceBase(
|
|||||||
|
|
||||||
ChatHistory chatHistory = [];
|
ChatHistory chatHistory = [];
|
||||||
|
|
||||||
chatHistory.AddSystemMessage(runtimeConfig.SystemPrompt);
|
var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
|
||||||
|
chatHistory.AddSystemMessage(systemPrompt);
|
||||||
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
||||||
chatHistory.AddUserMessage(prompt);
|
chatHistory.AddUserMessage(prompt);
|
||||||
|
|
||||||
|
|||||||
@@ -160,10 +160,10 @@
|
|||||||
<value>Active provider: {0}</value>
|
<value>Active provider: {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AIProvidersFlyoutHeader.Text" xml:space="preserve">
|
<data name="AIProvidersFlyoutHeader.Text" xml:space="preserve">
|
||||||
<value>AI providers</value>
|
<value>Configured models</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AIProvidersEmptyText.Text" xml:space="preserve">
|
<data name="AIProvidersEmptyText.Text" xml:space="preserve">
|
||||||
<value>No AI providers configured</value>
|
<value>No models configured</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AIProvidersManageButtonContent.Content" xml:space="preserve">
|
<data name="AIProvidersManageButtonContent.Content" xml:space="preserve">
|
||||||
<value>Configure models in Settings</value>
|
<value>Configure models in Settings</value>
|
||||||
@@ -364,8 +364,12 @@
|
|||||||
<data name="CustomEndpointWarning" xml:space="preserve">
|
<data name="CustomEndpointWarning" xml:space="preserve">
|
||||||
<value>You are using a custom endpoint. Verify all answers.</value>
|
<value>You are using a custom endpoint. Verify all answers.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LocalModelBadge" xml:space="preserve">
|
<data name="LocalModelBadge.Text" xml:space="preserve">
|
||||||
<value>Local</value>
|
<value>Local</value>
|
||||||
<comment>Badge label displayed next to local AI model providers (e.g., Ollama, Foundry Local) to indicate the model runs locally</comment>
|
<comment>Badge label displayed next to local AI model providers (e.g., Ollama, Foundry Local) to indicate the model runs locally</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FoundryLocal_UnableToLoadModel" xml:space="preserve">
|
||||||
|
<value>Unable to load Foundry Local model: {0}</value>
|
||||||
|
<comment>{0} is the model identifier. Do not translate {0}.</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -77,6 +77,7 @@ namespace AdvancedPaste.ViewModels
|
|||||||
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
|
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
|
||||||
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
|
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
|
||||||
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
|
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasLegalLinks))]
|
||||||
private bool _isAllowedByGPO;
|
private bool _isAllowedByGPO;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -222,6 +223,8 @@ namespace AdvancedPaste.ViewModels
|
|||||||
|
|
||||||
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
|
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
|
||||||
|
|
||||||
|
public bool HasLegalLinks => HasTermsLink || HasPrivacyLink;
|
||||||
|
|
||||||
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
||||||
|
|
||||||
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
||||||
@@ -367,6 +370,7 @@ namespace AdvancedPaste.ViewModels
|
|||||||
OnPropertyChanged(nameof(PrivacyLinkUri));
|
OnPropertyChanged(nameof(PrivacyLinkUri));
|
||||||
OnPropertyChanged(nameof(HasTermsLink));
|
OnPropertyChanged(nameof(HasTermsLink));
|
||||||
OnPropertyChanged(nameof(HasPrivacyLink));
|
OnPropertyChanged(nameof(HasPrivacyLink));
|
||||||
|
OnPropertyChanged(nameof(HasLegalLinks));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshPasteFormats()
|
private void RefreshPasteFormats()
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
|
|
||||||
if (wait == WAIT_OBJECT_0 + (hParent ? (hManualOverride ? 3 : 2) : 2))
|
if (wait == WAIT_OBJECT_0 + (hParent ? (hManualOverride ? 3 : 2) : 2))
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchService] Settings file changed event detected.");
|
|
||||||
ResetEvent(hSettingsChanged);
|
ResetEvent(hSettingsChanged);
|
||||||
LightSwitchSettings::instance().LoadSettings();
|
LightSwitchSettings::instance().LoadSettings();
|
||||||
stateManager.OnSettingsChanged();
|
stateManager.OnSettingsChanged();
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ LightSwitchStateManager::LightSwitchStateManager()
|
|||||||
void LightSwitchStateManager::OnSettingsChanged()
|
void LightSwitchStateManager::OnSettingsChanged()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_stateMutex);
|
std::lock_guard<std::mutex> lock(_stateMutex);
|
||||||
Logger::info(L"[LightSwitchStateManager] Settings changed event received");
|
|
||||||
|
|
||||||
// If manual override was active, clear it so new settings take effect
|
// If manual override was active, clear it so new settings take effect
|
||||||
if (_state.isManualOverride)
|
if (_state.isManualOverride)
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchStateManager] Clearing manual override due to settings update.");
|
|
||||||
_state.isManualOverride = false;
|
_state.isManualOverride = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +31,6 @@ void LightSwitchStateManager::OnSettingsChanged()
|
|||||||
void LightSwitchStateManager::OnTick(int currentMinutes)
|
void LightSwitchStateManager::OnTick(int currentMinutes)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_stateMutex);
|
std::lock_guard<std::mutex> lock(_stateMutex);
|
||||||
Logger::debug(L"[LightSwitchStateManager] Tick received: {}", currentMinutes);
|
|
||||||
EvaluateAndApplyIfNeeded();
|
EvaluateAndApplyIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +48,7 @@ void LightSwitchStateManager::OnManualOverride()
|
|||||||
|
|
||||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||||
|
|
||||||
Logger::info(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
|
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
|
||||||
(_state.isSystemLightActive ? L"light" : L"dark"),
|
(_state.isSystemLightActive ? L"light" : L"dark"),
|
||||||
(_state.isAppsLightActive ? L"light" : L"dark"));
|
(_state.isAppsLightActive ? L"light" : L"dark"));
|
||||||
}
|
}
|
||||||
@@ -79,9 +76,9 @@ void LightSwitchStateManager::SyncInitialThemeState()
|
|||||||
std::lock_guard<std::mutex> lock(_stateMutex);
|
std::lock_guard<std::mutex> lock(_stateMutex);
|
||||||
_state.isSystemLightActive = GetCurrentSystemTheme();
|
_state.isSystemLightActive = GetCurrentSystemTheme();
|
||||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||||
Logger::info(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
|
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
|
||||||
_state.isSystemLightActive ? L"light" : L"dark");
|
_state.isSystemLightActive ? L"light" : L"dark");
|
||||||
Logger::info(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
|
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
|
||||||
_state.isAppsLightActive ? L"light" : L"dark");
|
_state.isAppsLightActive ? L"light" : L"dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +124,6 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
// Early exit: OFF mode just pauses activity
|
// Early exit: OFF mode just pauses activity
|
||||||
if (_currentSettings.scheduleMode == ScheduleMode::Off)
|
if (_currentSettings.scheduleMode == ScheduleMode::Off)
|
||||||
{
|
{
|
||||||
Logger::debug(L"[LightSwitchStateManager] Mode is OFF — pausing service logic.");
|
|
||||||
_state.lastTickMinutes = now;
|
_state.lastTickMinutes = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,7 +141,6 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
|
|
||||||
if (newDay || modeChangedToSun)
|
if (newDay || modeChangedToSun)
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchStateManager] Recalculating sun times (mode/day change).");
|
|
||||||
auto [newLightTime, newDarkTime] = update_sun_times(_currentSettings);
|
auto [newLightTime, newDarkTime] = update_sun_times(_currentSettings);
|
||||||
_state.lastEvaluatedDay = st.wDay;
|
_state.lastEvaluatedDay = st.wDay;
|
||||||
_state.effectiveLightMinutes = newLightTime + _currentSettings.sunrise_offset;
|
_state.effectiveLightMinutes = newLightTime + _currentSettings.sunrise_offset;
|
||||||
@@ -188,12 +183,10 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
|
|
||||||
if (crossedBoundary)
|
if (crossedBoundary)
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchStateManager] Manual override cleared after crossing boundary.");
|
|
||||||
_state.isManualOverride = false;
|
_state.isManualOverride = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::debug(L"[LightSwitchStateManager] Manual override active — skipping auto apply.");
|
|
||||||
_state.lastTickMinutes = now;
|
_state.lastTickMinutes = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -206,7 +199,7 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
|
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
|
||||||
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
|
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
|
||||||
|
|
||||||
Logger::debug(
|
/* Logger::debug(
|
||||||
L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})",
|
L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})",
|
||||||
now / 60,
|
now / 60,
|
||||||
now % 60,
|
now % 60,
|
||||||
@@ -215,12 +208,12 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
_state.effectiveLightMinutes,
|
_state.effectiveLightMinutes,
|
||||||
_state.effectiveDarkMinutes / 60,
|
_state.effectiveDarkMinutes / 60,
|
||||||
_state.effectiveDarkMinutes % 60,
|
_state.effectiveDarkMinutes % 60,
|
||||||
_state.effectiveDarkMinutes);
|
_state.effectiveDarkMinutes); */
|
||||||
|
|
||||||
Logger::debug("should be light = {}, apps needs change = {}, system needs change = {}",
|
/* Logger::debug("should be light = {}, apps needs change = {}, system needs change = {}",
|
||||||
shouldBeLight ? "true" : "false",
|
shouldBeLight ? "true" : "false",
|
||||||
appsNeedsToChange ? "true" : "false",
|
appsNeedsToChange ? "true" : "false",
|
||||||
systemNeedsToChange ? "true" : "false");
|
systemNeedsToChange ? "true" : "false"); */
|
||||||
|
|
||||||
// Only apply theme if there's a change or no override active
|
// Only apply theme if there's a change or no override active
|
||||||
if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange))
|
if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange))
|
||||||
@@ -230,10 +223,6 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
|
|||||||
|
|
||||||
_state.isSystemLightActive = GetCurrentSystemTheme();
|
_state.isSystemLightActive = GetCurrentSystemTheme();
|
||||||
_state.isAppsLightActive = GetCurrentAppsTheme();
|
_state.isAppsLightActive = GetCurrentAppsTheme();
|
||||||
|
|
||||||
Logger::debug(L"[LightSwitchStateManager] Synced post-apply theme state — System: {}, Apps: {}",
|
|
||||||
_state.isSystemLightActive ? L"light" : L"dark",
|
|
||||||
_state.isAppsLightActive ? L"light" : L"dark");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.lastTickMinutes = now;
|
_state.lastTickMinutes = now;
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ type_pEnableThemeDialogTexture pEnableThemeDialogTexture;
|
|||||||
#define WIN7_VERSION 0x106
|
#define WIN7_VERSION 0x106
|
||||||
#define WIN10_VERSION 0x206
|
#define WIN10_VERSION 0x206
|
||||||
|
|
||||||
|
// Default recording format frame rates
|
||||||
|
#define RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE 15
|
||||||
|
#define RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE 30
|
||||||
|
|
||||||
// Time that we'll cache live zoom window to avoid flicker
|
// Time that we'll cache live zoom window to avoid flicker
|
||||||
// of live zooming on Vista/ws2k8
|
// of live zooming on Vista/ws2k8
|
||||||
#define LIVEZOOM_WINDOW_TIMEOUT 2*3600*1000
|
#define LIVEZOOM_WINDOW_TIMEOUT 2*3600*1000
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ LOGFONT g_LogFont;
|
|||||||
BOOLEAN g_DemoTypeUserDriven = false;
|
BOOLEAN g_DemoTypeUserDriven = false;
|
||||||
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
||||||
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
||||||
DWORD g_RecordFrameRate = 30;
|
DWORD g_RecordFrameRate = 30; // We default to 30 here, but g_RecordFrameRate can be different depending on recording format and gets set accordingly
|
||||||
DWORD g_RecordScaling = 100;
|
DWORD g_RecordScaling = 100;
|
||||||
DWORD g_RecordScalingGIF = 50;
|
DWORD g_RecordScalingGIF = 50;
|
||||||
DWORD g_RecordScalingMP4 = 100;
|
DWORD g_RecordScalingMP4 = 100;
|
||||||
RecordingFormat g_RecordingFormat = RecordingFormat::GIF;
|
RecordingFormat g_RecordingFormat = RecordingFormat::MP4;
|
||||||
BOOLEAN g_CaptureAudio = FALSE;
|
BOOLEAN g_CaptureAudio = FALSE;
|
||||||
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
||||||
|
|
||||||
@@ -87,8 +87,7 @@ REG_SETTING RegSettings[] = {
|
|||||||
{ L"SnapToGrid", SETTING_TYPE_BOOLEAN, 0, &g_SnapToGrid, static_cast<DOUBLE>(g_SnapToGrid) },
|
{ L"SnapToGrid", SETTING_TYPE_BOOLEAN, 0, &g_SnapToGrid, static_cast<DOUBLE>(g_SnapToGrid) },
|
||||||
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
||||||
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
||||||
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
|
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(g_RecordingFormat) },
|
||||||
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(0) },
|
|
||||||
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
|
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
|
||||||
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
|
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
|
||||||
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ BOOL g_RecordToggle = FALSE;
|
|||||||
BOOL g_RecordCropping = FALSE;
|
BOOL g_RecordCropping = FALSE;
|
||||||
SelectRectangle g_SelectRectangle;
|
SelectRectangle g_SelectRectangle;
|
||||||
std::wstring g_RecordingSaveLocation;
|
std::wstring g_RecordingSaveLocation;
|
||||||
|
std::wstring g_RecordingSaveLocationGIF;
|
||||||
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
|
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
|
||||||
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
|
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
|
||||||
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
|
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
|
||||||
@@ -2173,7 +2174,10 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
|||||||
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
|
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
|
||||||
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
|
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
|
||||||
|
|
||||||
for (int i = 0; i < _countof(g_FramerateOptions); i++) {
|
//
|
||||||
|
// The framerate drop down list is not used in the current version (might be added in the future)
|
||||||
|
//
|
||||||
|
/*for (int i = 0; i < _countof(g_FramerateOptions); i++) {
|
||||||
|
|
||||||
_stprintf(text, L"%d", g_FramerateOptions[i]);
|
_stprintf(text, L"%d", g_FramerateOptions[i]);
|
||||||
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_ADDSTRING),
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_ADDSTRING),
|
||||||
@@ -2182,7 +2186,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
|||||||
|
|
||||||
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(0));
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(0));
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Add the recording format to the combo box and set the current selection
|
// Add the recording format to the combo box and set the current selection
|
||||||
size_t selection = 0;
|
size_t selection = 0;
|
||||||
@@ -2345,17 +2349,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
|||||||
text[2] = 0;
|
text[2] = 0;
|
||||||
newTimeout = _tstoi( text );
|
newTimeout = _tstoi( text );
|
||||||
|
|
||||||
if( g_RecordingFormat == RecordingFormat::GIF )
|
|
||||||
{
|
|
||||||
// Hardcode lower frame rate for GIFs
|
|
||||||
g_RecordFrameRate = 15;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0))];
|
|
||||||
}
|
|
||||||
|
|
||||||
g_RecordingFormat = static_cast<RecordingFormat>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)));
|
g_RecordingFormat = static_cast<RecordingFormat>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)));
|
||||||
|
g_RecordFrameRate = (g_RecordingFormat == RecordingFormat::GIF) ? RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE : RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
||||||
g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
|
g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
|
||||||
|
|
||||||
// Get the selected microphone
|
// Get the selected microphone
|
||||||
@@ -3536,7 +3531,16 @@ void StopRecording()
|
|||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
auto GetUniqueRecordingFilename()
|
auto GetUniqueRecordingFilename()
|
||||||
{
|
{
|
||||||
std::filesystem::path path{ g_RecordingSaveLocation };
|
std::filesystem::path path;
|
||||||
|
|
||||||
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
||||||
|
{
|
||||||
|
path = g_RecordingSaveLocationGIF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = g_RecordingSaveLocation;
|
||||||
|
}
|
||||||
|
|
||||||
// Chop off index if it's there
|
// Chop off index if it's there
|
||||||
auto base = std::regex_replace( path.stem().wstring(), std::wregex( L" [(][0-9]+[)]$" ), L"" );
|
auto base = std::regex_replace( path.stem().wstring(), std::wregex( L" [(][0-9]+[)]$" ), L"" );
|
||||||
@@ -3591,6 +3595,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
|
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
|
||||||
|
|
||||||
// Create the appropriate recording session based on format
|
// Create the appropriate recording session based on format
|
||||||
|
OutputDebugStringW((L"Starting recording session. Framerate: " + std::to_wstring(g_RecordFrameRate) + L" scaling: " + std::to_wstring(g_RecordScaling) + L" Format: " + (g_RecordingFormat == RecordingFormat::GIF ? L"GIF" : L"MP4") + L"\n").c_str());
|
||||||
if (g_RecordingFormat == RecordingFormat::GIF)
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
||||||
{
|
{
|
||||||
g_GifRecordingSession = GifRecordingSession::Create(
|
g_GifRecordingSession = GifRecordingSession::Create(
|
||||||
@@ -3657,18 +3662,44 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( g_RecordingSaveLocation.size() == 0) {
|
// Peek the folder Windows has chosen to display
|
||||||
|
static std::filesystem::path lastSaveFolder;
|
||||||
|
wil::unique_cotaskmem_string chosenFolderPath;
|
||||||
|
wil::com_ptr<IShellItem> currentSelectedFolder;
|
||||||
|
bool bFolderChanged = false;
|
||||||
|
if (SUCCEEDED(saveDialog->GetFolder(currentSelectedFolder.put())))
|
||||||
|
{
|
||||||
|
if (SUCCEEDED(currentSelectedFolder->GetDisplayName(SIGDN_FILESYSPATH, chosenFolderPath.put())))
|
||||||
|
{
|
||||||
|
if (lastSaveFolder != chosenFolderPath.get())
|
||||||
|
{
|
||||||
|
lastSaveFolder = chosenFolderPath.get() ? chosenFolderPath.get() : std::filesystem::path{};
|
||||||
|
bFolderChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( (g_RecordingFormat == RecordingFormat::GIF && g_RecordingSaveLocationGIF.size() == 0) || (g_RecordingFormat == RecordingFormat::MP4 && g_RecordingSaveLocation.size() == 0) || (bFolderChanged)) {
|
||||||
|
|
||||||
wil::com_ptr<IShellItem> shellItem;
|
wil::com_ptr<IShellItem> shellItem;
|
||||||
wil::unique_cotaskmem_string folderPath;
|
wil::unique_cotaskmem_string folderPath;
|
||||||
if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put())))
|
if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put()))) {
|
||||||
g_RecordingSaveLocation = folderPath.get();
|
if (g_RecordingFormat == RecordingFormat::GIF) {
|
||||||
|
g_RecordingSaveLocationGIF = folderPath.get();
|
||||||
|
std::filesystem::path currentPath{ g_RecordingSaveLocationGIF };
|
||||||
|
g_RecordingSaveLocationGIF = currentPath / DEFAULT_GIF_RECORDING_FILE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g_RecordingSaveLocation = folderPath.get();
|
||||||
|
if (g_RecordingFormat == RecordingFormat::MP4) {
|
||||||
|
std::filesystem::path currentPath{ g_RecordingSaveLocation };
|
||||||
|
g_RecordingSaveLocation = currentPath / DEFAULT_RECORDING_FILE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always use appropriate default filename based on current format
|
// Always use appropriate default filename based on current format
|
||||||
std::filesystem::path currentPath{ g_RecordingSaveLocation };
|
|
||||||
const wchar_t* defaultFile = (g_RecordingFormat == RecordingFormat::GIF) ? DEFAULT_GIF_RECORDING_FILE : DEFAULT_RECORDING_FILE;
|
|
||||||
g_RecordingSaveLocation = currentPath.parent_path() / defaultFile;
|
|
||||||
auto suggestedName = GetUniqueRecordingFilename();
|
auto suggestedName = GetUniqueRecordingFilename();
|
||||||
saveDialog->SetFileName( suggestedName.c_str() );
|
saveDialog->SetFileName( suggestedName.c_str() );
|
||||||
|
|
||||||
@@ -3696,9 +3727,15 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
co_await file.MoveAndReplaceAsync( destFile );
|
co_await file.MoveAndReplaceAsync(destFile);
|
||||||
g_RecordingSaveLocation = file.Path();
|
if (g_RecordingFormat == RecordingFormat::GIF) {
|
||||||
SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
|
g_RecordingSaveLocationGIF = file.Path();
|
||||||
|
SaveToClipboard(g_RecordingSaveLocationGIF.c_str(), hWnd);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g_RecordingSaveLocation = file.Path();
|
||||||
|
SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_bSaveInProgress = false;
|
g_bSaveInProgress = false;
|
||||||
|
|
||||||
@@ -4039,8 +4076,10 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
// Set g_RecordScaling based on the current recording format
|
// Set g_RecordScaling based on the current recording format
|
||||||
if (g_RecordingFormat == RecordingFormat::GIF) {
|
if (g_RecordingFormat == RecordingFormat::GIF) {
|
||||||
g_RecordScaling = g_RecordScalingGIF;
|
g_RecordScaling = g_RecordScalingGIF;
|
||||||
|
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
|
||||||
} else {
|
} else {
|
||||||
g_RecordScaling = g_RecordScalingMP4;
|
g_RecordScaling = g_RecordScalingMP4;
|
||||||
|
g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// to support migrating from
|
// to support migrating from
|
||||||
@@ -6332,6 +6371,17 @@ LRESULT APIENTRY MainWndProc(
|
|||||||
{
|
{
|
||||||
// Reload the settings. This message is called from PowerToys after a setting is changed by the user.
|
// Reload the settings. This message is called from PowerToys after a setting is changed by the user.
|
||||||
reg.ReadRegSettings(RegSettings);
|
reg.ReadRegSettings(RegSettings);
|
||||||
|
|
||||||
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingGIF;
|
||||||
|
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingMP4;
|
||||||
|
g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply tray icon setting
|
// Apply tray icon setting
|
||||||
EnableDisableTrayIcon(hWnd, g_ShowTrayIcon);
|
EnableDisableTrayIcon(hWnd, g_ShowTrayIcon);
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
|
|
||||||
public string Title { get => string.IsNullOrEmpty(field) ? Name : field; protected set; } = string.Empty;
|
public string Title { get => string.IsNullOrEmpty(field) ? Name : field; protected set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Id { get; protected set; } = string.Empty;
|
||||||
|
|
||||||
// This property maps to `IPage.IsLoading`, but we want to expose our own
|
// This property maps to `IPage.IsLoading`, but we want to expose our own
|
||||||
// `IsLoading` property as a combo of this value and `IsInitialized`
|
// `IsLoading` property as a combo of this value and `IsInitialized`
|
||||||
public bool ModelIsLoading { get; protected set; } = true;
|
public bool ModelIsLoading { get; protected set; } = true;
|
||||||
@@ -142,6 +144,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
|
|||||||
return; // throw?
|
return; // throw?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Id = page.Id;
|
||||||
Name = page.Name;
|
Name = page.Name;
|
||||||
ModelIsLoading = page.IsLoading;
|
ModelIsLoading = page.IsLoading;
|
||||||
Title = page.Title;
|
Title = page.Title;
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ public class OpenPage : EventBase, IEvent
|
|||||||
{
|
{
|
||||||
public int PageDepth { get; set; }
|
public int PageDepth { get; set; }
|
||||||
|
|
||||||
public OpenPage(int pageDepth)
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public OpenPage(int pageDepth, string id)
|
||||||
{
|
{
|
||||||
PageDepth = pageDepth;
|
PageDepth = pageDepth;
|
||||||
|
Id = id;
|
||||||
|
|
||||||
EventName = "CmdPal_OpenPage";
|
EventName = "CmdPal_OpenPage";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ using Microsoft.PowerToys.Telemetry;
|
|||||||
using Microsoft.UI.Dispatching;
|
using Microsoft.UI.Dispatching;
|
||||||
using Microsoft.UI.Input;
|
using Microsoft.UI.Input;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Automation.Peers;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Input;
|
using Microsoft.UI.Xaml.Input;
|
||||||
using Microsoft.UI.Xaml.Media.Animation;
|
using Microsoft.UI.Xaml.Media.Animation;
|
||||||
@@ -160,7 +159,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
new AsyncNavigationRequest(message.Page, message.CancellationToken),
|
new AsyncNavigationRequest(message.Page, message.CancellationToken),
|
||||||
message.WithAnimation ? DefaultPageAnimation : _noAnimation);
|
message.WithAnimation ? DefaultPageAnimation : _noAnimation);
|
||||||
|
|
||||||
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
|
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth, message.Page.Id));
|
||||||
|
|
||||||
if (!ViewModel.IsNested)
|
if (!ViewModel.IsNested)
|
||||||
{
|
{
|
||||||
@@ -655,15 +654,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// The CommandBar is responsible for handling all the item keybindings,
|
// The CommandBar is responsible for handling all the item keybindings,
|
||||||
// since the bound context item may need to then show another
|
// since the bound context item may need to then show another
|
||||||
// context menu
|
// context menu
|
||||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||||
WeakReferenceMessenger.Default.Send(msg);
|
WeakReferenceMessenger.Default.Send(msg);
|
||||||
e.Handled = msg.Handled;
|
e.Handled = msg.Handled;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
|||||||
<value>More</value>
|
<value>More</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
|
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
|
||||||
<value>Last Position</value>
|
<value>Last position</value>
|
||||||
<comment>Reopen the window where it was last closed</comment>
|
<comment>Reopen the window where it was last closed</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="TrayMenu_Settings" xml:space="preserve">
|
<data name="TrayMenu_Settings" xml:space="preserve">
|
||||||
|
|||||||
@@ -24,13 +24,13 @@
|
|||||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<!-- <PropertyGroup>
|
||||||
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
<PropertyGroup Condition="'$(CIBuild)'=='true'">
|
||||||
<ApplicationManifest>ImageResizerUI.prod.manifest</ApplicationManifest>
|
<ApplicationManifest>ImageResizerUI.prod.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup> -->
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
|||||||
L"PowerToys.MouseJump.dll",
|
L"PowerToys.MouseJump.dll",
|
||||||
L"PowerToys.AlwaysOnTopModuleInterface.dll",
|
L"PowerToys.AlwaysOnTopModuleInterface.dll",
|
||||||
L"PowerToys.MousePointerCrosshairs.dll",
|
L"PowerToys.MousePointerCrosshairs.dll",
|
||||||
L"PowerToys.CursorWrap.dll",
|
// L"PowerToys.CursorWrap.dll",
|
||||||
L"PowerToys.PowerAccentModuleInterface.dll",
|
L"PowerToys.PowerAccentModuleInterface.dll",
|
||||||
L"PowerToys.PowerOCRModuleInterface.dll",
|
L"PowerToys.PowerOCRModuleInterface.dll",
|
||||||
L"PowerToys.AdvancedPasteModuleInterface.dll",
|
L"PowerToys.AdvancedPasteModuleInterface.dll",
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// 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.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Stores provider-specific configuration overrides so each AI service can keep distinct settings.
|
|
||||||
/// </summary>
|
|
||||||
public class AIProviderConfigurationSnapshot
|
|
||||||
{
|
|
||||||
[JsonPropertyName("model-name")]
|
|
||||||
public string ModelName { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("endpoint-url")]
|
|
||||||
public string EndpointUrl { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("api-version")]
|
|
||||||
public string ApiVersion { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("deployment-name")]
|
|
||||||
public string DeploymentName { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("model-path")]
|
|
||||||
public string ModelPath { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("system-prompt")]
|
|
||||||
public string SystemPrompt { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("moderation-enabled")]
|
|
||||||
public bool ModerationEnabled { get; set; } = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ public static class AIServiceTypeRegistry
|
|||||||
{
|
{
|
||||||
ServiceType = AIServiceType.AzureAIInference,
|
ServiceType = AIServiceType.AzureAIInference,
|
||||||
DisplayName = "Azure AI Inference",
|
DisplayName = "Azure AI Inference",
|
||||||
IconPath = "ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg", // No icon for Azure AI Inference, use Foundry Local temporarily
|
IconPath = "ms-appx:///Assets/Settings/Icons/Models/Azure.svg",
|
||||||
IsOnlineService = true,
|
IsOnlineService = true,
|
||||||
LegalDescription = "AdvancedPaste_AzureAIInference_LegalDescription",
|
LegalDescription = "AdvancedPaste_AzureAIInference_LegalDescription",
|
||||||
TermsLabel = "AdvancedPaste_AzureAIInference_TermsLabel",
|
TermsLabel = "AdvancedPaste_AzureAIInference_TermsLabel",
|
||||||
|
|||||||
@@ -13,56 +13,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AdvancedPasteMigrationHelper
|
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>
|
/// <summary>
|
||||||
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
|
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configuration">The configuration instance.</param>
|
/// <param name="configuration">The configuration instance.</param>
|
||||||
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
|
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
|
||||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
|
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)
|
if (configuration is null)
|
||||||
{
|
{
|
||||||
@@ -71,101 +27,45 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||||
|
|
||||||
var normalizedServiceType = NormalizeServiceType(serviceTypeKey);
|
const string serviceTypeKey = "OpenAI";
|
||||||
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, normalizedServiceType, StringComparison.OrdinalIgnoreCase));
|
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, serviceTypeKey, StringComparison.OrdinalIgnoreCase));
|
||||||
bool configurationUpdated = false;
|
bool updated = false;
|
||||||
|
|
||||||
if (existingProvider is null)
|
if (existingProvider is null)
|
||||||
{
|
{
|
||||||
existingProvider = CreateProvider(normalizedServiceType, snapshot);
|
existingProvider = CreateProvider(serviceTypeKey);
|
||||||
configuration.Providers.Add(existingProvider);
|
configuration.Providers.Add(existingProvider);
|
||||||
configurationUpdated = true;
|
updated = true;
|
||||||
}
|
|
||||||
else if (snapshot is not null)
|
|
||||||
{
|
|
||||||
configurationUpdated |= ApplySnapshot(existingProvider, snapshot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configurationUpdated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
updated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
||||||
|
|
||||||
return (existingProvider, configurationUpdated);
|
return (existingProvider, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeServiceType(string serviceTypeKey)
|
/// <summary>
|
||||||
{
|
/// Creates a provider with default values for the requested service type.
|
||||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
/// </summary>
|
||||||
return serviceType.ToConfigurationString();
|
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey)
|
||||||
}
|
|
||||||
|
|
||||||
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
|
|
||||||
{
|
{
|
||||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
var serviceType = serviceTypeKey.ToAIServiceType();
|
||||||
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||||
var provider = new PasteAIProviderDefinition
|
var provider = new PasteAIProviderDefinition
|
||||||
{
|
{
|
||||||
ServiceType = serviceTypeKey,
|
ServiceType = serviceTypeKey,
|
||||||
ModelName = !string.IsNullOrWhiteSpace(snapshot?.ModelName) ? snapshot.ModelName : PasteAIProviderDefaults.GetDefaultModelName(serviceType),
|
ModelName = PasteAIProviderDefaults.GetDefaultModelName(serviceType),
|
||||||
EndpointUrl = snapshot?.EndpointUrl ?? string.Empty,
|
EndpointUrl = string.Empty,
|
||||||
ApiVersion = snapshot?.ApiVersion ?? string.Empty,
|
ApiVersion = string.Empty,
|
||||||
DeploymentName = snapshot?.DeploymentName ?? string.Empty,
|
DeploymentName = string.Empty,
|
||||||
ModelPath = snapshot?.ModelPath ?? string.Empty,
|
ModelPath = string.Empty,
|
||||||
SystemPrompt = snapshot?.SystemPrompt ?? string.Empty,
|
SystemPrompt = string.Empty,
|
||||||
ModerationEnabled = snapshot?.ModerationEnabled ?? true,
|
ModerationEnabled = serviceType == AIServiceType.OpenAI,
|
||||||
IsLocalModel = metadata.IsLocalModel,
|
IsLocalModel = metadata.IsLocalModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
return provider;
|
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)
|
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
|
||||||
{
|
{
|
||||||
if (configuration?.Providers is null || configuration.Providers.Count == 0)
|
if (configuration?.Providers is null || configuration.Providers.Count == 0)
|
||||||
|
|||||||
@@ -34,52 +34,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||||
public bool IsAIEnabled { get; set; }
|
public bool IsAIEnabled { get; set; }
|
||||||
|
|
||||||
[JsonExtensionData]
|
|
||||||
public Dictionary<string, JsonElement> ExtensionData
|
|
||||||
{
|
|
||||||
get => _extensionData;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_extensionData = value;
|
|
||||||
|
|
||||||
if (_extensionData != null && _extensionData.TryGetValue("IsOpenAIEnabled", out var legacyElement) && legacyElement.ValueKind == JsonValueKind.Object && legacyElement.TryGetProperty("value", out var valueElement))
|
|
||||||
{
|
|
||||||
IsAIEnabled = valueElement.ValueKind switch
|
|
||||||
{
|
|
||||||
JsonValueKind.True => true,
|
|
||||||
JsonValueKind.False => false,
|
|
||||||
_ => IsAIEnabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
_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 bool? _legacyAdvancedAIEnabled;
|
private bool? _legacyAdvancedAIEnabled;
|
||||||
|
|
||||||
[JsonPropertyName("IsAdvancedAIEnabled")]
|
[JsonPropertyName("IsAdvancedAIEnabled")]
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
{
|
{
|
||||||
private string _activeProviderId = string.Empty;
|
private string _activeProviderId = string.Empty;
|
||||||
private ObservableCollection<PasteAIProviderDefinition> _providers = new();
|
private ObservableCollection<PasteAIProviderDefinition> _providers = new();
|
||||||
private bool _useSharedCredentials = true;
|
|
||||||
private Dictionary<string, AIProviderConfigurationSnapshot> _legacyProviderConfigurations;
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
@@ -39,21 +37,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
set => SetProperty(ref _providers, value ?? new ObservableCollection<PasteAIProviderDefinition>());
|
set => SetProperty(ref _providers, value ?? new ObservableCollection<PasteAIProviderDefinition>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonPropertyName("use-shared-credentials")]
|
|
||||||
public bool UseSharedCredentials
|
|
||||||
{
|
|
||||||
get => _useSharedCredentials;
|
|
||||||
set => SetProperty(ref _useSharedCredentials, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("provider-configurations")]
|
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
||||||
public Dictionary<string, AIProviderConfigurationSnapshot> LegacyProviderConfigurations
|
|
||||||
{
|
|
||||||
get => _legacyProviderConfigurations;
|
|
||||||
set => _legacyProviderConfigurations = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public PasteAIProviderDefinition ActiveProvider
|
public PasteAIProviderDefinition ActiveProvider
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,59 +1,34 @@
|
|||||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_2092_1741)">
|
<path d="M10.2663 0H0.231885C0.103572 0 0 0.10358 0 0.231885V2.55072C0 2.67904 0.103572 2.78261 0.231885 2.78261H12.5217C12.9059 2.78261 13.2174 3.09411 13.2174 3.47826V3.18995C13.2174 1.53971 11.9861 0 10.2663 0Z" fill="url(#paint0_linear_178_3940)"/>
|
||||||
<mask id="mask0_2092_1741" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="17" height="16">
|
<path d="M12.2334 0.81543C12.8633 1.44693 13.2174 2.29872 13.2174 3.19069V15.7689C13.2174 15.8972 13.3209 16.0007 13.4492 16.0007H15.7681C15.8964 16.0007 16 15.8972 16 15.7689V5.73524C16 4.99707 15.707 4.28983 15.1853 3.76732L12.2334 0.81543Z" fill="url(#paint1_linear_178_3940)"/>
|
||||||
<path d="M16.5 0H0.5V16H16.5V0Z" fill="white"/>
|
<path d="M6.78804 3.47852H0.231885C0.103572 3.47852 0 3.58209 0 3.71039V6.02921C0 6.1575 0.103572 6.26112 0.231885 6.26112H9.04346C9.42759 6.26112 9.7391 6.57263 9.7391 6.95676V6.6685C9.7391 5.01822 8.50778 3.47852 6.78804 3.47852Z" fill="url(#paint2_linear_178_3940)"/>
|
||||||
</mask>
|
<path d="M8.75537 4.29297C9.38531 4.92446 9.73928 5.77628 9.73928 6.6682V15.7681C9.73928 15.8964 9.8429 16 9.97119 16H12.29C12.4183 16 12.5219 15.8964 12.5219 15.7681V9.21281C12.5219 8.47462 12.229 7.76735 11.7072 7.24482L8.75537 4.29297Z" fill="url(#paint3_linear_178_3940)"/>
|
||||||
<g mask="url(#mask0_2092_1741)">
|
<path d="M3.30975 6.95703H0.231885C0.103572 6.95703 0 7.06056 0 7.18886V9.50771C0 9.63609 0.103572 9.73962 0.231885 9.73962H5.56521C5.94936 9.73962 6.26087 10.0511 6.26087 10.4353V10.147C6.26087 8.49675 5.02956 6.95703 3.30975 6.95703Z" fill="url(#paint4_linear_178_3940)"/>
|
||||||
<mask id="mask1_2092_1741" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="-2" width="19" height="20">
|
<path d="M5.27686 7.77148C5.90677 8.40302 6.26083 9.25477 6.26083 10.1468V15.7684C6.26083 15.8967 6.36436 16.0003 6.49274 16.0003H8.8115C8.93988 16.0003 9.04341 15.8967 9.04341 15.7684V12.6913C9.04341 11.9531 8.75051 11.2459 8.22874 10.7234L5.27686 7.77148Z" fill="url(#paint5_linear_178_3940)"/>
|
||||||
<path d="M17.8337 -1.33337H-0.833008V17.3333H17.8337V-1.33337Z" fill="white"/>
|
|
||||||
</mask>
|
|
||||||
<g mask="url(#mask1_2092_1741)">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1137 0.315668C11.57 0.315668 11.9744 0.657891 12.1196 1.15567C12.2648 1.65345 13.1152 4.73345 13.1152 4.73345V10.852H10.0352L10.0974 0.305298H11.1137V0.315668Z" fill="url(#paint0_linear_2092_1741)"/>
|
|
||||||
<path d="M15.6352 5.09586C15.6352 4.87808 15.4589 4.71216 15.2515 4.71216H13.4366C12.1611 4.71216 11.124 5.7492 11.124 7.02472V10.8618H13.3226C14.5982 10.8618 15.6352 9.82472 15.6352 8.54919V5.09586Z" fill="url(#paint1_linear_2092_1741)"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1133 0.315674C10.7607 0.315674 10.4807 0.595674 10.4807 0.948265L10.4185 12.5942C10.4185 14.2949 9.0392 15.6742 7.33847 15.6742H1.74885C1.47921 15.6742 1.30292 15.4149 1.38589 15.1661L5.86589 2.37938C6.30144 1.14531 7.46293 0.315674 8.7696 0.315674H11.1237H11.1133Z" fill="url(#paint2_linear_2092_1741)"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="paint0_linear_2092_1741" x1="12.3996" y1="11.0801" x2="9.80702" y2="0.699373" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint0_linear_178_3940" x1="13.2174" y1="3.15349" x2="0" y2="3.15349" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#712575"/>
|
<stop stop-color="#2C08AC"/>
|
||||||
<stop offset="0.09" stop-color="#9A2884"/>
|
<stop offset="0.8" stop-color="#4F42FD"/>
|
||||||
<stop offset="0.18" stop-color="#BF2C92"/>
|
|
||||||
<stop offset="0.27" stop-color="#DA2E9C"/>
|
|
||||||
<stop offset="0.34" stop-color="#EB30A2"/>
|
|
||||||
<stop offset="0.4" stop-color="#F131A5"/>
|
|
||||||
<stop offset="0.5" stop-color="#EC30A3"/>
|
|
||||||
<stop offset="0.61" stop-color="#DF2F9E"/>
|
|
||||||
<stop offset="0.72" stop-color="#C92D96"/>
|
|
||||||
<stop offset="0.83" stop-color="#AA2A8A"/>
|
|
||||||
<stop offset="0.95" stop-color="#83267C"/>
|
|
||||||
<stop offset="1" stop-color="#712575"/>
|
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint1_linear_2092_1741" x1="13.3848" y1="0.532897" x2="13.3848" y2="15.1759" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint1_linear_178_3940" x1="14.2303" y1="0.81543" x2="23.44" y2="11.9747" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#DA7ED0"/>
|
<stop offset="0.3" stop-color="#7274FF"/>
|
||||||
<stop offset="0.08" stop-color="#B17BD5"/>
|
<stop offset="1" stop-color="#4F42FD"/>
|
||||||
<stop offset="0.19" stop-color="#8778DB"/>
|
|
||||||
<stop offset="0.3" stop-color="#6276E1"/>
|
|
||||||
<stop offset="0.41" stop-color="#4574E5"/>
|
|
||||||
<stop offset="0.54" stop-color="#2E72E8"/>
|
|
||||||
<stop offset="0.67" stop-color="#1D71EB"/>
|
|
||||||
<stop offset="0.81" stop-color="#1471EC"/>
|
|
||||||
<stop offset="1" stop-color="#1171ED"/>
|
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="paint2_linear_2092_1741" x1="12.5029" y1="0.865306" x2="2.79625" y2="16.4313" gradientUnits="userSpaceOnUse">
|
<linearGradient id="paint2_linear_178_3940" x1="9.7391" y1="6.63202" x2="0" y2="6.63202" gradientUnits="userSpaceOnUse">
|
||||||
<stop stop-color="#DA7ED0"/>
|
<stop stop-color="#2C08AC"/>
|
||||||
<stop offset="0.05" stop-color="#B77BD4"/>
|
<stop offset="0.8" stop-color="#4F42FD"/>
|
||||||
<stop offset="0.11" stop-color="#9079DA"/>
|
</linearGradient>
|
||||||
<stop offset="0.18" stop-color="#6E77DF"/>
|
<linearGradient id="paint3_linear_178_3940" x1="10.7523" y1="4.29297" x2="17.3026" y2="14.5881" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0.25" stop-color="#5175E3"/>
|
<stop offset="0.3" stop-color="#7274FF"/>
|
||||||
<stop offset="0.33" stop-color="#3973E7"/>
|
<stop offset="1" stop-color="#4F42FD"/>
|
||||||
<stop offset="0.42" stop-color="#2772E9"/>
|
</linearGradient>
|
||||||
<stop offset="0.54" stop-color="#1A71EB"/>
|
<linearGradient id="paint4_linear_178_3940" x1="6.26087" y1="9.91172" x2="0" y2="9.91172" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0.68" stop-color="#1371EC"/>
|
<stop stop-color="#2C08AC"/>
|
||||||
<stop offset="1" stop-color="#1171ED"/>
|
<stop offset="0.8" stop-color="#4F42FD"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint5_linear_178_3940" x1="7.2738" y1="7.77148" x2="11.0624" y2="16.243" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.3" stop-color="#7274FF"/>
|
||||||
|
<stop offset="1" stop-color="#4F42FD"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<clipPath id="clip0_2092_1741">
|
|
||||||
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 318 KiB |
@@ -28,6 +28,8 @@
|
|||||||
<None Remove="Assets\Settings\Icons\Models\WindowsML.svg" />
|
<None Remove="Assets\Settings\Icons\Models\WindowsML.svg" />
|
||||||
<None Remove="Assets\Settings\Modules\APDialog.dark.png" />
|
<None Remove="Assets\Settings\Modules\APDialog.dark.png" />
|
||||||
<None Remove="Assets\Settings\Modules\APDialog.light.png" />
|
<None Remove="Assets\Settings\Modules\APDialog.light.png" />
|
||||||
|
<None Remove="Assets\Settings\Modules\CmdPal_Background.png" />
|
||||||
|
<None Remove="Assets\Settings\Modules\CmdPal_Hero.png" />
|
||||||
<None Remove="Assets\Settings\Modules\LightSwitch.png" />
|
<None Remove="Assets\Settings\Modules\LightSwitch.png" />
|
||||||
<None Remove="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml" />
|
<None Remove="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml" />
|
||||||
<None Remove="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml" />
|
<None Remove="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml" />
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||||
|
|||||||
@@ -30,6 +30,24 @@
|
|||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<InfoBar
|
||||||
|
x:Uid="AdvancedPaste_FL_PreviewMessage"
|
||||||
|
Grid.Row="1"
|
||||||
|
Padding="4"
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="Foundry Local is still in Public Preview">
|
||||||
|
<InfoBar.ActionButton>
|
||||||
|
<HyperlinkButton
|
||||||
|
x:Uid="AdvancedPaste_FL_LearnMoreFoundryLocal"
|
||||||
|
Content="Learn more"
|
||||||
|
NavigateUri="https://learn.microsoft.com/azure/ai-foundry/foundry-local/what-is-foundry-local" />
|
||||||
|
</InfoBar.ActionButton>
|
||||||
|
</InfoBar>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
x:Name="LoadingPanel"
|
x:Name="LoadingPanel"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -69,12 +87,12 @@
|
|||||||
FontSize="24"
|
FontSize="24"
|
||||||
Glyph="" />
|
Glyph="" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="AdvancedPaste_FL_NoModelsDownloaded."
|
x:Uid="AdvancedPaste_FL_NoModelsDownloaded"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||||
TextAlignment="Center" />
|
TextAlignment="Center" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="AdvancedPaste_FL_RunFoundryLocalText.Text"
|
x:Uid="AdvancedPaste_FL_RunFoundryLocalText"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
@@ -106,7 +124,7 @@
|
|||||||
<ComboBox.Header>
|
<ComboBox.Header>
|
||||||
<TextBlock>
|
<TextBlock>
|
||||||
<Run x:Uid="AdvancedPaste_FL_LocalModel" /><LineBreak /><Run
|
<Run x:Uid="AdvancedPaste_FL_LocalModel" /><LineBreak /><Run
|
||||||
x:Uid="AdvancedPaste_FL_UseCLIToDownloadModels"
|
x:Uid="AdvancedPaste_FL_UseCliToDownloadModels"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@@ -152,7 +170,7 @@
|
|||||||
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"
|
x:Uid="AdvancedPaste_FL_FLNotAvailableYet"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl
|
|||||||
|
|
||||||
public delegate void DownloadRequestedEventHandler(object sender, object payload);
|
public delegate void DownloadRequestedEventHandler(object sender, object payload);
|
||||||
|
|
||||||
public delegate void LoadRequestedEventHandler(object sender, FoundryLoadRequestedEventArgs args);
|
public delegate void LoadRequestedEventHandler(object sender);
|
||||||
|
|
||||||
public event ModelSelectionChangedEventHandler SelectionChanged;
|
public event ModelSelectionChangedEventHandler SelectionChanged;
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl
|
|||||||
|
|
||||||
public bool HasDownloadableModels => DownloadableModels?.Cast<object>().Any() ?? false;
|
public bool HasDownloadableModels => DownloadableModels?.Cast<object>().Any() ?? false;
|
||||||
|
|
||||||
public void RequestLoad(bool refresh)
|
public void RequestLoad()
|
||||||
{
|
{
|
||||||
if (IsLoading)
|
if (IsLoading)
|
||||||
{
|
{
|
||||||
@@ -107,7 +107,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl
|
|||||||
|
|
||||||
IsAvailable = false;
|
IsAvailable = false;
|
||||||
StatusText = "Loading Foundry Local status...";
|
StatusText = "Loading Foundry Local status...";
|
||||||
LoadRequested?.Invoke(this, new FoundryLoadRequestedEventArgs(refresh));
|
LoadRequested?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnCachedModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
private static void OnCachedModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
@@ -310,7 +310,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl
|
|||||||
|
|
||||||
private void RefreshModelsButton_Click(object sender, RoutedEventArgs e)
|
private void RefreshModelsButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
RequestLoad(refresh: true);
|
RequestLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateVisualStates()
|
private void UpdateVisualStates()
|
||||||
@@ -444,14 +444,4 @@ public sealed partial class FoundryLocalModelPicker : UserControl
|
|||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(license) ? Visibility.Collapsed : Visibility.Visible;
|
return string.IsNullOrWhiteSpace(license) ? Visibility.Collapsed : Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FoundryLoadRequestedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public FoundryLoadRequestedEventArgs(bool refresh)
|
|
||||||
{
|
|
||||||
Refresh = refresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Refresh { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,9 @@
|
|||||||
</InfoBar>
|
</InfoBar>
|
||||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
|
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
|
||||||
|
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
|
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
|
||||||
ContentAlignment="Left"
|
ContentAlignment="Left"
|
||||||
@@ -177,9 +180,6 @@
|
|||||||
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" ContentAlignment="Left">
|
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" ContentAlignment="Left">
|
||||||
<CheckBox x:Uid="AdvancedPaste_CloseAfterLosingFocus" IsChecked="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
|
<CheckBox x:Uid="AdvancedPaste_CloseAfterLosingFocus" IsChecked="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
|
|
||||||
<CheckBox x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
|
|
||||||
</tkcontrols:SettingsCard>
|
|
||||||
<tkcontrols:SettingsCard Name="AdvancedPasteShowCustomPreviewSettingsCard" ContentAlignment="Left">
|
<tkcontrols:SettingsCard Name="AdvancedPasteShowCustomPreviewSettingsCard" ContentAlignment="Left">
|
||||||
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_ShowCustomPreviewSettingsCard" IsChecked="{x:Bind ViewModel.ShowCustomPreview, Mode=TwoWay}" />
|
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_ShowCustomPreviewSettingsCard" IsChecked="{x:Bind ViewModel.ShowCustomPreview, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
@@ -429,12 +429,11 @@
|
|||||||
<!-- Paste AI provider dialog -->
|
<!-- Paste AI provider dialog -->
|
||||||
<ContentDialog
|
<ContentDialog
|
||||||
x:Name="PasteAIProviderConfigurationDialog"
|
x:Name="PasteAIProviderConfigurationDialog"
|
||||||
|
x:Uid="AdvancedPaste_EndpointDialog"
|
||||||
Title="Paste with AI provider configuration"
|
Title="Paste with AI provider configuration"
|
||||||
Closed="PasteAIProviderConfigurationDialog_Closed"
|
Closed="PasteAIProviderConfigurationDialog_Closed"
|
||||||
PrimaryButtonClick="PasteAIProviderConfigurationDialog_PrimaryButtonClick"
|
PrimaryButtonClick="PasteAIProviderConfigurationDialog_PrimaryButtonClick"
|
||||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
|
||||||
PrimaryButtonText="Save"
|
|
||||||
SecondaryButtonText="Cancel">
|
|
||||||
<ContentDialog.Resources>
|
<ContentDialog.Resources>
|
||||||
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
|
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
|
||||||
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable
|
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<ModelDetails> _foundryCachedModels = new();
|
private readonly ObservableCollection<ModelDetails> _foundryCachedModels = new();
|
||||||
private readonly ObservableCollection<FoundryDownloadableModel> _foundryDownloadableModels = new();
|
|
||||||
private CancellationTokenSource _foundryModelLoadCts;
|
private CancellationTokenSource _foundryModelLoadCts;
|
||||||
private bool _suppressFoundrySelectionChanged;
|
private bool _suppressFoundrySelectionChanged;
|
||||||
private bool _isFoundryLocalAvailable;
|
private bool _isFoundryLocalAvailable;
|
||||||
@@ -57,7 +56,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
if (FoundryLocalPicker is not null)
|
if (FoundryLocalPicker is not null)
|
||||||
{
|
{
|
||||||
FoundryLocalPicker.CachedModels = _foundryCachedModels;
|
FoundryLocalPicker.CachedModels = _foundryCachedModels;
|
||||||
FoundryLocalPicker.DownloadableModels = _foundryDownloadableModels;
|
|
||||||
FoundryLocalPicker.SelectionChanged += FoundryLocalPicker_SelectionChanged;
|
FoundryLocalPicker.SelectionChanged += FoundryLocalPicker_SelectionChanged;
|
||||||
FoundryLocalPicker.LoadRequested += FoundryLocalPicker_LoadRequested;
|
FoundryLocalPicker.LoadRequested += FoundryLocalPicker_LoadRequested;
|
||||||
}
|
}
|
||||||
@@ -84,7 +82,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
{
|
{
|
||||||
ViewModel.RefreshEnabledState();
|
ViewModel.RefreshEnabledState();
|
||||||
UpdatePasteAIUIVisibility();
|
UpdatePasteAIUIVisibility();
|
||||||
_ = UpdateFoundryLocalUIAsync(refreshFoundry: true);
|
_ = UpdateFoundryLocalUIAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnableAdvancedPasteAI() => ViewModel.EnableAI();
|
private void EnableAdvancedPasteAI() => ViewModel.EnableAI();
|
||||||
@@ -386,7 +384,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task UpdateFoundryLocalUIAsync(bool refreshFoundry = false)
|
private Task UpdateFoundryLocalUIAsync()
|
||||||
{
|
{
|
||||||
string selectedType = ViewModel?.PasteAIProviderDraft?.ServiceType ?? string.Empty;
|
string selectedType = ViewModel?.PasteAIProviderDraft?.ServiceType ?? string.Empty;
|
||||||
bool isFoundryLocal = string.Equals(selectedType, "FoundryLocal", StringComparison.OrdinalIgnoreCase);
|
bool isFoundryLocal = string.Equals(selectedType, "FoundryLocal", StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -421,12 +419,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FoundryLocalPicker?.RequestLoad(refreshFoundry);
|
FoundryLocalPicker?.RequestLoad();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadFoundryLocalModelsAsync(bool refresh = false)
|
private async Task LoadFoundryLocalModelsAsync()
|
||||||
{
|
{
|
||||||
if (FoundryLocalPanel is null)
|
if (FoundryLocalPanel is null)
|
||||||
{
|
{
|
||||||
@@ -458,9 +456,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<ModelDetails> cachedModelsEnumerable = refresh
|
IEnumerable<ModelDetails> cachedModelsEnumerable = await provider.GetModelsAsync(cancelationToken: cancellationToken).ConfigureAwait(false);
|
||||||
? await provider.GetModelsAsync(ignoreCached: true, cancelationToken: cancellationToken)
|
|
||||||
: await provider.GetModelsAsync(cancelationToken: cancellationToken);
|
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -469,9 +465,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
|
|
||||||
var cachedModels = cachedModelsEnumerable?.ToList() ?? new List<ModelDetails>();
|
var cachedModels = cachedModelsEnumerable?.ToList() ?? new List<ModelDetails>();
|
||||||
|
|
||||||
UpdateFoundryCollections(cachedModels, []);
|
DispatcherQueue.TryEnqueue(() =>
|
||||||
ShowFoundryAvailableState();
|
{
|
||||||
RestoreFoundrySelection(cachedModels);
|
UpdateFoundryCollections(cachedModels);
|
||||||
|
ShowFoundryAvailableState();
|
||||||
|
RestoreFoundrySelection(cachedModels);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -480,12 +479,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var errorMessage = $"Unable to load Foundry Local models. {ex.Message}";
|
var errorMessage = $"Unable to load Foundry Local models. {ex.Message}";
|
||||||
ShowFoundryUnavailableState(errorMessage);
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[AdvancedPastePage] Failed to load Foundry Local models: {ex}");
|
System.Diagnostics.Debug.WriteLine($"[AdvancedPastePage] Failed to load Foundry Local models: {ex}");
|
||||||
|
DispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
ShowFoundryUnavailableState(errorMessage);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
UpdateFoundrySaveButtonState();
|
DispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
UpdateFoundrySaveButtonState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +543,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
UpdateFoundrySaveButtonState();
|
UpdateFoundrySaveButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels, IReadOnlyCollection<ModelDetails> catalogModels)
|
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels)
|
||||||
{
|
{
|
||||||
_foundryCachedModels.Clear();
|
_foundryCachedModels.Clear();
|
||||||
|
|
||||||
@@ -547,20 +552,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
_foundryCachedModels.Add(model);
|
_foundryCachedModels.Add(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => NormalizeFoundryModelReference(m.Url ?? m.Name)), StringComparer.OrdinalIgnoreCase);
|
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => m.Name), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_foundryDownloadableModels.Clear();
|
|
||||||
|
|
||||||
foreach (var model in catalogModels.OrderBy(m => m.Name, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var reference = NormalizeFoundryModelReference(model.Url ?? model.Name);
|
|
||||||
if (cachedReferences.Contains(reference))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_foundryDownloadableModels.Add(new FoundryDownloadableModel(model));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestoreFoundrySelection(IReadOnlyCollection<ModelDetails> cachedModels)
|
private void RestoreFoundrySelection(IReadOnlyCollection<ModelDetails> cachedModels)
|
||||||
@@ -576,9 +568,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(currentModelReference))
|
if (!string.IsNullOrWhiteSpace(currentModelReference))
|
||||||
{
|
{
|
||||||
var normalizedReference = NormalizeFoundryModelReference(currentModelReference);
|
|
||||||
matchingModel = cachedModels.FirstOrDefault(model =>
|
matchingModel = cachedModels.FirstOrDefault(model =>
|
||||||
string.Equals(NormalizeFoundryModelReference(model.Url ?? model.Name), normalizedReference, StringComparison.OrdinalIgnoreCase));
|
string.Equals(model.Name, currentModelReference, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FoundryLocalPicker is null)
|
if (FoundryLocalPicker is null)
|
||||||
@@ -608,7 +599,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
{
|
{
|
||||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||||
{
|
{
|
||||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(matchingModel.Url ?? matchingModel.Name);
|
ViewModel.PasteAIProviderDraft.ModelName = matchingModel.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FoundryLocalPicker is not null)
|
if (FoundryLocalPicker is not null)
|
||||||
@@ -620,19 +611,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
UpdateFoundrySaveButtonState();
|
UpdateFoundrySaveButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeFoundryModelReference(string modelReference)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(modelReference))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix = FoundryLocalModelProvider.Instance.UrlPrefix;
|
|
||||||
return modelReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
|
|
||||||
? modelReference
|
|
||||||
: $"{prefix}{modelReference}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateFoundrySaveButtonState()
|
private void UpdateFoundrySaveButtonState()
|
||||||
{
|
{
|
||||||
if (PasteAIProviderConfigurationDialog is null)
|
if (PasteAIProviderConfigurationDialog is null)
|
||||||
@@ -656,7 +634,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isFoundryLocalAvailable || _foundryDownloadableModels.Any(model => model.IsDownloading))
|
if (!_isFoundryLocalAvailable)
|
||||||
{
|
{
|
||||||
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
||||||
return;
|
return;
|
||||||
@@ -677,7 +655,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
{
|
{
|
||||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||||
{
|
{
|
||||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(selectedModel.Url ?? selectedModel.Name);
|
ViewModel.PasteAIProviderDraft.ModelName = selectedModel.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FoundryLocalPicker is not null)
|
if (FoundryLocalPicker is not null)
|
||||||
@@ -701,9 +679,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
UpdateFoundrySaveButtonState();
|
UpdateFoundrySaveButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void FoundryLocalPicker_LoadRequested(object sender, FoundryLocalModelPicker.FoundryLoadRequestedEventArgs args)
|
private async void FoundryLocalPicker_LoadRequested(object sender)
|
||||||
{
|
{
|
||||||
await LoadFoundryLocalModelsAsync(args?.Refresh ?? false);
|
await LoadFoundryLocalModelsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FoundryDownloadableModel : INotifyPropertyChanged
|
private sealed class FoundryDownloadableModel : INotifyPropertyChanged
|
||||||
@@ -1118,7 +1096,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
PasteAIProviderConfigurationDialog.Title = $"{displayName} provider configuration";
|
PasteAIProviderConfigurationDialog.Title = $"{displayName} provider configuration";
|
||||||
}
|
}
|
||||||
|
|
||||||
await UpdateFoundryLocalUIAsync(refreshFoundry: true);
|
await UpdateFoundryLocalUIAsync();
|
||||||
UpdatePasteAIUIVisibility();
|
UpdatePasteAIUIVisibility();
|
||||||
RefreshDialogBindings();
|
RefreshDialogBindings();
|
||||||
|
|
||||||
@@ -1147,7 +1125,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
: $"{titlePrefix} provider configuration";
|
: $"{titlePrefix} provider configuration";
|
||||||
|
|
||||||
UpdatePasteAIUIVisibility();
|
UpdatePasteAIUIVisibility();
|
||||||
await UpdateFoundryLocalUIAsync(refreshFoundry: false);
|
await UpdateFoundryLocalUIAsync();
|
||||||
RefreshDialogBindings();
|
RefreshDialogBindings();
|
||||||
PasteAIApiKeyPasswordBox.Password = ViewModel.GetPasteAIApiKey(provider.Id, provider.ServiceType);
|
PasteAIApiKeyPasswordBox.Password = ViewModel.GetPasteAIApiKey(provider.Id, provider.ServiceType);
|
||||||
await PasteAIProviderConfigurationDialog.ShowAsync();
|
await PasteAIProviderConfigurationDialog.ShowAsync();
|
||||||
|
|||||||
@@ -10,40 +10,202 @@
|
|||||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||||
AutomationProperties.LandmarkType="Main"
|
AutomationProperties.LandmarkType="Main"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer AutomationProperties.AutomationId="PageScrollViewer">
|
||||||
|
<Grid
|
||||||
|
MaxWidth="1000"
|
||||||
|
Padding="16,0,16,0"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="16"
|
||||||
|
RowSpacing="8">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<tkcontrols:OpacityMaskView Margin="-16,0,-16,0" HorizontalAlignment="Stretch">
|
||||||
|
<tkcontrols:OpacityMaskView.OpacityMask>
|
||||||
|
<Rectangle>
|
||||||
|
<Rectangle.Fill>
|
||||||
|
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
|
||||||
|
<GradientStop Offset="0.50" Color="Black" />
|
||||||
|
<GradientStop Offset="0.75" Color="#80000000" />
|
||||||
|
<GradientStop Offset="0.95" Color="Transparent" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Rectangle.Fill>
|
||||||
|
</Rectangle>
|
||||||
|
</tkcontrols:OpacityMaskView.OpacityMask>
|
||||||
|
<Grid Height="560">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Image
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Source="/Assets/Settings/Modules/CmdPal_Background.png"
|
||||||
|
Stretch="UniformToFill" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,24,0,12"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="36"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="Command Palette">
|
||||||
|
<TextBlock.Foreground>
|
||||||
|
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||||
|
<GradientStop Offset="0.0" Color="#FFB9EBFF" />
|
||||||
|
<GradientStop Offset="0.49" Color="#FF86CBFF" />
|
||||||
|
<GradientStop Offset="1.0" Color="#FFA1E7FF" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</TextBlock.Foreground>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="White"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<Run x:Uid="CmdPal_Description" />
|
||||||
|
<Hyperlink NavigateUri="">
|
||||||
|
<Run x:Uid="LearnMore_CmdPal.Text" Foreground="White" />
|
||||||
|
</Hyperlink>
|
||||||
|
</TextBlock>
|
||||||
|
<Image
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0,16,0,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Source="/Assets/Settings/Modules/CmdPal_Hero.png"
|
||||||
|
Stretch="Uniform" />
|
||||||
|
</Grid>
|
||||||
|
</tkcontrols:OpacityMaskView>
|
||||||
|
|
||||||
<controls:SettingsPageControl x:Uid="CmdPal" ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdPal.png">
|
<Grid
|
||||||
<controls:SettingsPageControl.ModuleContent>
|
Grid.Row="1"
|
||||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
Margin="0,-12,0,24"
|
||||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
ColumnSpacing="32"
|
||||||
<tkcontrols:SettingsCard
|
RowSpacing="8">
|
||||||
Name="CmdPalEnableCmdPal"
|
<Grid.RowDefinitions>
|
||||||
x:Uid="CmdPal_Enable_CmdPal"
|
<RowDefinition Height="Auto" />
|
||||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}">
|
<RowDefinition Height="Auto" />
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
</Grid.RowDefinitions>
|
||||||
</tkcontrols:SettingsCard>
|
<Grid.ColumnDefinitions>
|
||||||
</controls:GPOInfoControl>
|
<ColumnDefinition Width="*" />
|
||||||
<controls:SettingsGroup x:Uid="CmdPal_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
<ColumnDefinition Width="*" />
|
||||||
<tkcontrols:SettingsCard
|
<ColumnDefinition Width="*" />
|
||||||
Name="CmdPalActivationShortcut"
|
</Grid.ColumnDefinitions>
|
||||||
x:Uid="CmdPal_ActivationShortcut"
|
<FontIcon
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
HorizontalAlignment="Center"
|
||||||
<controls:ShortcutControl
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
Glyph="" />
|
||||||
HotkeySettings="{x:Bind Path=ViewModel.Hotkey, Mode=OneWay}"
|
<TextBlock
|
||||||
IsEnabled="False" />
|
Grid.Row="1"
|
||||||
<tkcontrols:SettingsCard.Description>
|
HorizontalAlignment="Center"
|
||||||
<HyperlinkButton
|
TextAlignment="Center"
|
||||||
x:Name="CmdPalSettingsDeeplink"
|
TextWrapping="Wrap">
|
||||||
x:Uid="CmdPal_DeeplinkContent"
|
<Run x:Uid="CmdPal_ExtensibleHeader" FontWeight="SemiBold" /> <LineBreak />
|
||||||
Click="CmdPalSettingsDeeplink_Click" />
|
<Run
|
||||||
</tkcontrols:SettingsCard.Description>
|
x:Uid="CmdPal_ExtensibleDescription"
|
||||||
</tkcontrols:SettingsCard>
|
FontSize="12"
|
||||||
</controls:SettingsGroup>
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
</StackPanel>
|
</TextBlock>
|
||||||
</controls:SettingsPageControl.ModuleContent>
|
|
||||||
|
|
||||||
<controls:SettingsPageControl.PrimaryLinks>
|
<FontIcon
|
||||||
<controls:PageLink x:Uid="LearnMore_CmdPal" Link="https://aka.ms/PowerToysOverview_CmdPal" />
|
Grid.Column="1"
|
||||||
</controls:SettingsPageControl.PrimaryLinks>
|
HorizontalAlignment="Center"
|
||||||
</controls:SettingsPageControl>
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Glyph="" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<Run x:Uid="CmdPal_FastHeader" FontWeight="SemiBold" /> <LineBreak />
|
||||||
|
<Run
|
||||||
|
x:Uid="CmdPal_FastDescription"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<FontIcon
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Glyph="" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<Run x:Uid="CmdPal_ModernHeader" FontWeight="SemiBold" /> <LineBreak />
|
||||||
|
<Run
|
||||||
|
x:Uid="CmdPal_ModernDescription"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="{StaticResource SettingsCardSpacing}">
|
||||||
|
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||||
|
<tkcontrols:SettingsCard
|
||||||
|
Name="CmdPalEnableCmdPal"
|
||||||
|
x:Uid="CmdPal_Enable_CmdPal"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}">
|
||||||
|
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
</controls:GPOInfoControl>
|
||||||
|
<tkcontrols:SettingsCard
|
||||||
|
x:Uid="CmdPal_Launch"
|
||||||
|
Grid.Row="3"
|
||||||
|
ActionIcon="{ui:FontIcon Glyph=}"
|
||||||
|
Click="LaunchCard_Click"
|
||||||
|
Header="Launch Command Palette"
|
||||||
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||||
|
<ItemsControl
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
IsTabStop="False"
|
||||||
|
ItemsSource="{x:Bind Path=ViewModel.Hotkey.GetKeysList(), Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<controls:KeyVisual
|
||||||
|
Padding="8,8,8,8"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
Content="{Binding}"
|
||||||
|
Style="{StaticResource AccentKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard
|
||||||
|
x:Uid="CmdPal_Settings"
|
||||||
|
Grid.Row="4"
|
||||||
|
ActionIcon="{ui:FontIcon Glyph=}"
|
||||||
|
Click="SettingsCard_Click"
|
||||||
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
</local:NavigablePage>
|
</local:NavigablePage>
|
||||||
|
|||||||
@@ -63,12 +63,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CmdPalSettingsDeeplink_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
private void SettingsCard_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// Launch CmdPal settings window as normal user using explorer
|
// Launch CmdPal settings window as normal user using explorer
|
||||||
string launchPath = "explorer.exe";
|
string launchPath = "explorer.exe";
|
||||||
string launchArgs = "x-cmdpal://settings";
|
string launchArgs = "x-cmdpal://settings";
|
||||||
LaunchApp(launchPath, launchArgs);
|
LaunchApp(launchPath, launchArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LaunchCard_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Launch CmdPal window as normal user using explorer
|
||||||
|
string launchPath = "explorer.exe";
|
||||||
|
string launchArgs = "x-cmdpal:";
|
||||||
|
LaunchApp(launchPath, launchArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,26 +273,35 @@
|
|||||||
|
|
||||||
<panels:MouseJumpPanel x:Name="MouseUtils_MouseJump_Panel" x:Uid="MouseUtils_MouseJump_Panel" />
|
<panels:MouseJumpPanel x:Name="MouseUtils_MouseJump_Panel" x:Uid="MouseUtils_MouseJump_Panel" />
|
||||||
|
|
||||||
|
<!--
|
||||||
<controls:SettingsGroup x:Uid="MouseUtils_CursorWrap" AutomationProperties.AutomationId="MouseUtils_CursorWrapTestId">
|
<controls:SettingsGroup x:Uid="MouseUtils_CursorWrap" AutomationProperties.AutomationId="MouseUtils_CursorWrapTestId">
|
||||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay}">
|
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsExpander
|
<tkcontrols:SettingsCard
|
||||||
Name="MouseUtilsEnableCursorWrap"
|
Name="MouseUtilsEnableCursorWrap"
|
||||||
x:Uid="MouseUtils_Enable_CursorWrap"
|
x:Uid="MouseUtils_Enable_CursorWrap"
|
||||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CursorWrap.png}"
|
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CursorWrap.png}"
|
||||||
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||||
IsExpanded="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsExpander.Items>
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
|
||||||
<CheckBox x:Uid="MouseUtils_AutoActivate" IsChecked="{x:Bind ViewModel.CursorWrapAutoActivate, Mode=TwoWay}" />
|
|
||||||
</tkcontrols:SettingsCard>
|
|
||||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
|
||||||
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
|
|
||||||
</tkcontrols:SettingsCard>
|
|
||||||
</tkcontrols:SettingsExpander.Items>
|
|
||||||
</tkcontrols:SettingsExpander>
|
|
||||||
</controls:GPOInfoControl>
|
</controls:GPOInfoControl>
|
||||||
</controls:SettingsGroup>
|
|
||||||
|
<tkcontrols:SettingsExpander
|
||||||
|
Name="MouseUtilsCursorWrapSettingsExpander"
|
||||||
|
x:Uid="MouseUtils_CursorWrap_ActivationShortcut"
|
||||||
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}"
|
||||||
|
IsExpanded="True">
|
||||||
|
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.CursorWrapActivationShortcut, Mode=TwoWay}" />
|
||||||
|
<tkcontrols:SettingsExpander.Items>
|
||||||
|
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||||
|
<CheckBox x:Uid="MouseUtils_AutoActivate" IsChecked="{x:Bind ViewModel.CursorWrapAutoActivate, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
||||||
|
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
</tkcontrols:SettingsExpander.Items>
|
||||||
|
</tkcontrols:SettingsExpander>
|
||||||
|
</controls:SettingsGroup>-->
|
||||||
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs" AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsTestId">
|
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs" AutomationProperties.AutomationId="MouseUtils_MousePointerCrosshairsTestId">
|
||||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">
|
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
|
|||||||
@@ -285,9 +285,6 @@
|
|||||||
AutomationProperties.AutomationId="InputOutputNavItem"
|
AutomationProperties.AutomationId="InputOutputNavItem"
|
||||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
|
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
|
||||||
SelectsOnInvoked="False">
|
SelectsOnInvoked="False">
|
||||||
<NavigationViewItem.InfoBadge>
|
|
||||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
|
||||||
</NavigationViewItem.InfoBadge>
|
|
||||||
<NavigationViewItem.MenuItems>
|
<NavigationViewItem.MenuItems>
|
||||||
<NavigationViewItem
|
<NavigationViewItem
|
||||||
x:Name="KeyboardManagerNavigationItem"
|
x:Name="KeyboardManagerNavigationItem"
|
||||||
@@ -302,11 +299,7 @@
|
|||||||
x:Uid="Shell_MouseUtilities"
|
x:Uid="Shell_MouseUtilities"
|
||||||
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
|
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
|
||||||
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
|
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
|
||||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}">
|
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
|
||||||
<NavigationViewItem.InfoBadge>
|
|
||||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
|
||||||
</NavigationViewItem.InfoBadge>
|
|
||||||
</NavigationViewItem>
|
|
||||||
<NavigationViewItem
|
<NavigationViewItem
|
||||||
x:Name="MouseWithoutBordersNavigationItem"
|
x:Name="MouseWithoutBordersNavigationItem"
|
||||||
x:Uid="Shell_MouseWithoutBorders"
|
x:Uid="Shell_MouseWithoutBorders"
|
||||||
|
|||||||
@@ -648,10 +648,10 @@ Please review the placeholder content that represents the final terms and usage
|
|||||||
<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>Show what's currently on your Clipboard and access your Clipboard history</value>
|
<value>Access 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>View and select previously copied items</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
|
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
|
||||||
<value>Actions</value>
|
<value>Actions</value>
|
||||||
@@ -2685,10 +2685,7 @@ From there, simply click on one of the supported files in the File Explorer and
|
|||||||
<value>Wrap the mouse cursor between monitor edges</value>
|
<value>Wrap the mouse cursor between monitor edges</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
|
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
|
||||||
<value>Activation shortcut</value>
|
<value>Activation and behavior</value>
|
||||||
</data>
|
|
||||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Description.Text" xml:space="preserve">
|
|
||||||
<value>Hotkey to toggle cursor wrapping on/off</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
|
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
|
||||||
<value>Set shortcut</value>
|
<value>Set shortcut</value>
|
||||||
@@ -2706,7 +2703,7 @@ From there, simply click on one of the supported files in the File Explorer and
|
|||||||
<value>Mouse Pointer Crosshairs</value>
|
<value>Mouse Pointer Crosshairs</value>
|
||||||
<comment>Mouse as in the hardware peripheral.</comment>
|
<comment>Mouse as in the hardware peripheral.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Oobe_MouseUtils_MousePointerCrosshairs.Description" xml:space="preserve">
|
<data name="Oobe_MouseUtils_MousePointerCrosshairs_Description.Text" xml:space="preserve">
|
||||||
<value>Draw crosshairs centered around the mouse pointer.</value>
|
<value>Draw crosshairs centered around the mouse pointer.</value>
|
||||||
<comment>Mouse as in the hardware peripheral.</comment>
|
<comment>Mouse as in the hardware peripheral.</comment>
|
||||||
</data>
|
</data>
|
||||||
@@ -4601,7 +4598,7 @@ Activate by holding the key for the character you want to add an accent to, then
|
|||||||
<value>Automatically close the 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="AdvancedPaste_EnableClipboardPreview.Content" xml:space="preserve">
|
<data name="AdvancedPaste_EnableClipboardPreview.Header" xml:space="preserve">
|
||||||
<value>Show clipboard preview</value>
|
<value>Show clipboard preview</value>
|
||||||
<comment>Enables display of clipboard contents preview in the Advanced Paste window</comment>
|
<comment>Enables display of clipboard contents preview in the Advanced Paste window</comment>
|
||||||
</data>
|
</data>
|
||||||
@@ -5162,25 +5159,12 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="Shell_TopLevelSystemTools.Content" xml:space="preserve">
|
<data name="Shell_TopLevelSystemTools.Content" xml:space="preserve">
|
||||||
<value>System Tools</value>
|
<value>System Tools</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CmdPal.ModuleTitle" xml:space="preserve">
|
|
||||||
<value>Command Palette</value>
|
|
||||||
</data>
|
|
||||||
<data name="CmdPal_ShortDescription" xml:space="preserve">
|
|
||||||
<value>A better quick launcher</value>
|
|
||||||
</data>
|
|
||||||
<data name="CmdPal_ActivationDescription" xml:space="preserve">
|
|
||||||
<value>Open Command Palette</value>
|
|
||||||
</data>
|
|
||||||
<data name="CmdPal_Enable_CmdPal.Header" xml:space="preserve">
|
<data name="CmdPal_Enable_CmdPal.Header" xml:space="preserve">
|
||||||
<value>Enable Command Palette</value>
|
<value>Enable Command Palette</value>
|
||||||
<comment>"Command Palette" is the name of the utility.</comment>
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
</data>
|
|
||||||
<data name="CmdPal.ModuleDescription" xml:space="preserve">
|
|
||||||
<value>A fully extensible quick launcher with a richer display and additional capabilities without sacrificing performance.</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="LearnMore_CmdPal.Text" xml:space="preserve">
|
<data name="LearnMore_CmdPal.Text" xml:space="preserve">
|
||||||
<value>Learn more about Command Palette</value>
|
<value>Learn more</value>
|
||||||
<comment>Command Palette is a product name, do not loc</comment>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="Shell_CmdPal.Content" xml:space="preserve">
|
<data name="Shell_CmdPal.Content" xml:space="preserve">
|
||||||
<value>Command Palette</value>
|
<value>Command Palette</value>
|
||||||
@@ -5188,11 +5172,11 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
</data>
|
</data>
|
||||||
<data name="Oobe_CmdPal.Description" xml:space="preserve">
|
<data name="Oobe_CmdPal.Description" xml:space="preserve">
|
||||||
<value>A fully extensible quick launcher with a richer display and additional capabilities without sacrificing performance.</value>
|
<value>A fully extensible quick launcher with a richer display and additional capabilities without sacrificing performance.</value>
|
||||||
<comment>"Command Palette" is a product name</comment>
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Oobe_CmdPal.Title" xml:space="preserve">
|
<data name="Oobe_CmdPal.Title" xml:space="preserve">
|
||||||
<value>Command Palette</value>
|
<value>Command Palette</value>
|
||||||
<comment>"Command Palette" is a product name</comment>
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Oobe_CmdPal_HowToUse.Text" xml:space="preserve">
|
<data name="Oobe_CmdPal_HowToUse.Text" xml:space="preserve">
|
||||||
<value>and start typing!</value>
|
<value>and start typing!</value>
|
||||||
@@ -5225,14 +5209,8 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="RetryLabel.Text" xml:space="preserve">
|
<data name="RetryLabel.Text" xml:space="preserve">
|
||||||
<value>Retry</value>
|
<value>Retry</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CmdPal_Activation_GroupSettings.Header" xml:space="preserve">
|
<data name="CmdPal_Settings.Header" xml:space="preserve">
|
||||||
<value>Activation</value>
|
<value>Settings</value>
|
||||||
</data>
|
|
||||||
<data name="CmdPal_ActivationShortcut.Header" xml:space="preserve">
|
|
||||||
<value>Activation shortcut</value>
|
|
||||||
</data>
|
|
||||||
<data name="CmdPal_DeeplinkContent.Content" xml:space="preserve">
|
|
||||||
<value>Open Command Palette settings to customize the activation shortcut</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="Help_chromaCIE" xml:space="preserve">
|
<data name="Help_chromaCIE" xml:space="preserve">
|
||||||
<value>chroma (CIE LCh)</value>
|
<value>chroma (CIE LCh)</value>
|
||||||
@@ -5696,14 +5674,14 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<value>Foundry Local model</value>
|
<value>Foundry Local model</value>
|
||||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedPaste_FL_UseCLIToDownloadModels.Text" xml:space="preserve">
|
<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>
|
<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>
|
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedPaste_FL_RefreshModelList.Text" xml:space="preserve">
|
<data name="AdvancedPaste_FL_RefreshModelList.Text" xml:space="preserve">
|
||||||
<value>Refresh model list</value>
|
<value>Refresh model list</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AdvancedPaste_FL_FLNotavailableYet.Text" xml:space="preserve">
|
<data name="AdvancedPaste_FL_FLNotAvailableYet.Text" xml:space="preserve">
|
||||||
<value>Foundry Local is not available on this device yet.</value>
|
<value>Foundry Local is not available on this device yet.</value>
|
||||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||||
</data>
|
</data>
|
||||||
@@ -5747,4 +5725,60 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
|
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
|
||||||
<value>System prompt</value>
|
<value>System prompt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AdvancedPaste_EndpointDialog.PrimaryButtonText" xml:space="preserve">
|
||||||
|
<value>Save</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedPaste_EndpointDialog.SecondaryButtonText" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedPaste_EnableClipboardPreview.Description" xml:space="preserve">
|
||||||
|
<value>Display a preview of the current clipboard content</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedPaste_FL_LearnMoreFoundryLocal.Content" xml:space="preserve">
|
||||||
|
<value>Learn more</value>
|
||||||
|
</data>
|
||||||
|
<data name="AdvancedPaste_FL_PreviewMessage.Message" xml:space="preserve">
|
||||||
|
<value>Foundry Local is still in public preview</value>
|
||||||
|
<comment>Do not loc "Foundry Local"</comment>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_Settings.Description" xml:space="preserve">
|
||||||
|
<value>Configure the activation shortcut, extensions, behavior and much more</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_Launch.Header" xml:space="preserve">
|
||||||
|
<value>Open Command Palette</value>
|
||||||
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ShortDescription" xml:space="preserve">
|
||||||
|
<value>A better quick launcher</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_Description.Text" xml:space="preserve">
|
||||||
|
<value>Find files, launch apps, and do so much more with the most extensible quick launcher.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal.ModuleTitle" xml:space="preserve">
|
||||||
|
<value>Command Palette</value>
|
||||||
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ActivationDescription" xml:space="preserve">
|
||||||
|
<value>Open Command Palette</value>
|
||||||
|
<comment>Command Palette is a product name, do not loc</comment>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ExtensibleDescription.Text" xml:space="preserve">
|
||||||
|
<value>Powerful extensions help you do more</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ExtensibleHeader.Text" xml:space="preserve">
|
||||||
|
<value>Extensible</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_FastDescription.Text" xml:space="preserve">
|
||||||
|
<value>Find files and launch apps in an instant</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_FastHeader.Text" xml:space="preserve">
|
||||||
|
<value>Fast</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ModernHeader.Text" xml:space="preserve">
|
||||||
|
<value>Beautiful</value>
|
||||||
|
</data>
|
||||||
|
<data name="CmdPal_ModernDescription.Text" xml:space="preserve">
|
||||||
|
<value>A modern UI built with Fluent Design</value>
|
||||||
|
<comment>Fluent Design is a product name, do not loc</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -191,6 +191,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||||
|
|
||||||
|
if (legacyCredential is null)
|
||||||
|
{
|
||||||
|
if (legacyAdvancedAIConsumed)
|
||||||
|
{
|
||||||
|
SaveAndNotifySettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var configuration = properties.PasteAIConfiguration;
|
var configuration = properties.PasteAIConfiguration;
|
||||||
if (configuration is null)
|
if (configuration is null)
|
||||||
{
|
{
|
||||||
@@ -198,28 +210,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
properties.PasteAIConfiguration = configuration;
|
properties.PasteAIConfiguration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
|
|
||||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
|
||||||
|
|
||||||
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool configurationUpdated = false;
|
bool configurationUpdated = false;
|
||||||
|
|
||||||
if (hasLegacyProviders)
|
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||||
{
|
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
configurationUpdated |= ensureResult.Updated;
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||||
{
|
{
|
||||||
@@ -233,10 +228,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
RemoveLegacyOpenAICredential();
|
RemoveLegacyOpenAICredential();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool shouldEnableAI = true;
|
||||||
bool enabledChanged = false;
|
bool enabledChanged = false;
|
||||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
if (properties.IsAIEnabled != shouldEnableAI)
|
||||||
{
|
{
|
||||||
properties.IsAIEnabled = true;
|
properties.IsAIEnabled = shouldEnableAI;
|
||||||
enabledChanged = true;
|
enabledChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,11 +1243,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.UseSharedCredentials != incoming.UseSharedCredentials)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentProviders = current.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
var currentProviders = current.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
||||||
var incomingProviders = incoming.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
var incomingProviders = incoming.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
||||||
|
|
||||||
@@ -1407,8 +1398,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal)
|
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal))
|
||||||
|| string.Equals(e.PropertyName, nameof(PasteAIConfiguration.UseSharedCredentials), StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
SaveAndNotifySettings();
|
SaveAndNotifySettings();
|
||||||
}
|
}
|
||||||
@@ -1425,15 +1415,7 @@ 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)
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
|
|
||||||
public ObservableCollection<DashboardListItem> ActionModules { get; set; } = new ObservableCollection<DashboardListItem>();
|
public ObservableCollection<DashboardListItem> ActionModules { get; set; } = new ObservableCollection<DashboardListItem>();
|
||||||
|
|
||||||
|
// Master list of module items that is sorted and projected into AllModules.
|
||||||
|
private List<DashboardListItem> _moduleItems = new List<DashboardListItem>();
|
||||||
|
|
||||||
|
// Flag to prevent circular updates when a UI toggle triggers settings changes.
|
||||||
|
private bool _isUpdatingFromUI;
|
||||||
|
|
||||||
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
|
private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
|
||||||
|
|
||||||
public AllHotkeyConflictsData AllHotkeyConflictsData
|
public AllHotkeyConflictsData AllHotkeyConflictsData
|
||||||
@@ -74,7 +80,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
generalSettingsConfig.DashboardSortOrder = value;
|
generalSettingsConfig.DashboardSortOrder = value;
|
||||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
|
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettingsConfig);
|
||||||
SendConfigMSG(outgoing.ToString());
|
SendConfigMSG(outgoing.ToString());
|
||||||
RefreshModuleList();
|
SortModuleList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,8 +102,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
// set the callback functions value to handle outgoing IPC message.
|
// set the callback functions value to handle outgoing IPC message.
|
||||||
SendConfigMSG = ipcMSGCallBackFunc;
|
SendConfigMSG = ipcMSGCallBackFunc;
|
||||||
|
|
||||||
RefreshModuleList();
|
BuildModuleList();
|
||||||
GetShortcutModules();
|
SortModuleList();
|
||||||
|
RefreshShortcutModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
|
protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
|
||||||
@@ -129,14 +136,22 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
|
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshModuleList()
|
/// <summary>
|
||||||
|
/// Builds the master list of module items. Called once during initialization.
|
||||||
|
/// Each module item contains its configuration, enabled state, and GPO lock status.
|
||||||
|
/// </summary>
|
||||||
|
private void BuildModuleList()
|
||||||
{
|
{
|
||||||
AllModules.Clear();
|
_moduleItems.Clear();
|
||||||
|
|
||||||
var moduleItems = new List<DashboardListItem>();
|
|
||||||
|
|
||||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||||
{
|
{
|
||||||
|
// Hide CursorWrap from Dashboard
|
||||||
|
if (moduleType == ModuleType.CursorWrap)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
GpoRuleConfigured gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType);
|
GpoRuleConfigured gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType);
|
||||||
var newItem = new DashboardListItem()
|
var newItem = new DashboardListItem()
|
||||||
{
|
{
|
||||||
@@ -145,51 +160,148 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)),
|
IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)),
|
||||||
IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled,
|
IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled,
|
||||||
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||||
IsNew = moduleType == ModuleType.CursorWrap,
|
|
||||||
|
// IsNew = moduleType == ModuleType.CursorWrap,
|
||||||
DashboardModuleItems = GetModuleItems(moduleType),
|
DashboardModuleItems = GetModuleItems(moduleType),
|
||||||
};
|
};
|
||||||
newItem.EnabledChangedCallback = EnabledChangedOnUI;
|
newItem.EnabledChangedCallback = EnabledChangedOnUI;
|
||||||
moduleItems.Add(newItem);
|
_moduleItems.Add(newItem);
|
||||||
}
|
|
||||||
|
|
||||||
// Sort based on current sort order
|
|
||||||
var sortedItems = DashboardSortOrder switch
|
|
||||||
{
|
|
||||||
DashboardSortOrder.ByStatus => moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label),
|
|
||||||
_ => moduleItems.OrderBy(x => x.Label), // Default alphabetical
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var item in sortedItems)
|
|
||||||
{
|
|
||||||
AllModules.Add(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the module list according to the current sort order and updates the AllModules collection.
|
||||||
|
/// On first call, populates AllModules. On subsequent calls, uses Move() to reorder items in-place
|
||||||
|
/// to avoid destroying and recreating UI elements.
|
||||||
|
/// </summary>
|
||||||
|
private void SortModuleList()
|
||||||
|
{
|
||||||
|
var sortedItems = (DashboardSortOrder switch
|
||||||
|
{
|
||||||
|
DashboardSortOrder.ByStatus => _moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label),
|
||||||
|
_ => _moduleItems.OrderBy(x => x.Label), // Default alphabetical
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
// If AllModules is empty (first load), just populate it.
|
||||||
|
if (AllModules.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var item in sortedItems)
|
||||||
|
{
|
||||||
|
AllModules.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, update the collection in place using Move to avoid UI glitches.
|
||||||
|
for (int i = 0; i < sortedItems.Count; i++)
|
||||||
|
{
|
||||||
|
var currentItem = sortedItems[i];
|
||||||
|
var currentIndex = AllModules.IndexOf(currentItem);
|
||||||
|
|
||||||
|
if (currentIndex != -1 && currentIndex != i)
|
||||||
|
{
|
||||||
|
AllModules.Move(currentIndex, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify that DashboardSortOrder changed to update menu check mark.
|
||||||
|
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes module enabled/locked states by re-reading GPO configuration. Only
|
||||||
|
/// updates properties that have actually changed to minimize UI notifications
|
||||||
|
/// then re-sorts the list according to the current sort order.
|
||||||
|
/// </summary>
|
||||||
|
private void RefreshModuleList()
|
||||||
|
{
|
||||||
|
foreach (var item in _moduleItems)
|
||||||
|
{
|
||||||
|
GpoRuleConfigured gpo = ModuleHelper.GetModuleGpoConfiguration(item.Tag);
|
||||||
|
|
||||||
|
// GPO can force-enable (Enabled) or force-disable (Disabled) a module.
|
||||||
|
// If Enabled: module is on and the user cannot disable it.
|
||||||
|
// If Disabled: module is off and the user cannot enable it.
|
||||||
|
// Otherwise, the setting is unlocked and the user can enable/disable it.
|
||||||
|
bool newEnabledState = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, item.Tag));
|
||||||
|
|
||||||
|
// Lock the toggle when GPO is controlling the module.
|
||||||
|
bool newLockedState = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled;
|
||||||
|
|
||||||
|
// Only update if there's an actual change to minimize UI notifications.
|
||||||
|
if (item.IsEnabled != newEnabledState)
|
||||||
|
{
|
||||||
|
item.IsEnabled = newEnabledState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.IsLocked != newLockedState)
|
||||||
|
{
|
||||||
|
item.IsLocked = newLockedState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortModuleList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback invoked when a user toggles a module's enabled state in the UI.
|
||||||
|
/// Sets the _isUpdatingFromUI flag to prevent circular updates, then updates
|
||||||
|
/// settings, re-sorts if needed, and refreshes dependent collections.
|
||||||
|
/// </summary>
|
||||||
private void EnabledChangedOnUI(DashboardListItem dashboardListItem)
|
private void EnabledChangedOnUI(DashboardListItem dashboardListItem)
|
||||||
{
|
{
|
||||||
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled);
|
_isUpdatingFromUI = true;
|
||||||
|
try
|
||||||
if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true)
|
|
||||||
{
|
{
|
||||||
var settingsUtils = new SettingsUtils();
|
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled);
|
||||||
var settings = NewPlusViewModel.LoadSettings(settingsUtils);
|
|
||||||
NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request updated conflicts after module state change
|
if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true)
|
||||||
RequestConflictData();
|
{
|
||||||
|
var settingsUtils = new SettingsUtils();
|
||||||
|
var settings = NewPlusViewModel.LoadSettings(settingsUtils);
|
||||||
|
NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-sort only required if sorting by enabled status.
|
||||||
|
if (DashboardSortOrder == DashboardSortOrder.ByStatus)
|
||||||
|
{
|
||||||
|
SortModuleList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always refresh shortcuts/actions to reflect enabled state changes.
|
||||||
|
RefreshShortcutModules();
|
||||||
|
|
||||||
|
// Request updated conflicts after module state change.
|
||||||
|
RequestConflictData();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isUpdatingFromUI = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback invoked when module enabled state changes from other parts of the
|
||||||
|
/// settings UI. Ignores the notification if it was triggered by a UI toggle
|
||||||
|
/// we're already handling, to prevent circular updates.
|
||||||
|
/// </summary>
|
||||||
public void ModuleEnabledChangedOnSettingsPage()
|
public void ModuleEnabledChangedOnSettingsPage()
|
||||||
{
|
{
|
||||||
|
// Ignore if this was triggered by a UI change that we're already handling.
|
||||||
|
if (_isUpdatingFromUI)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RefreshModuleList();
|
RefreshModuleList();
|
||||||
GetShortcutModules();
|
RefreshShortcutModules();
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ShortcutModules));
|
OnPropertyChanged(nameof(ShortcutModules));
|
||||||
|
|
||||||
// Request updated conflicts after module state change
|
// Request updated conflicts after module state change.
|
||||||
RequestConflictData();
|
RequestConflictData();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -198,7 +310,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetShortcutModules()
|
/// <summary>
|
||||||
|
/// Rebuilds ShortcutModules and ActionModules collections by filtering AllModules
|
||||||
|
/// to only include enabled modules and their respective shortcut/action items.
|
||||||
|
/// </summary>
|
||||||
|
private void RefreshShortcutModules()
|
||||||
{
|
{
|
||||||
ShortcutModules.Clear();
|
ShortcutModules.Clear();
|
||||||
ActionModules.Clear();
|
ActionModules.Clear();
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
|
|
||||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||||
{
|
{
|
||||||
|
// Hide CursorWrap from All Apps flyout
|
||||||
|
if (moduleType == ModuleType.CursorWrap)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
AddFlyoutMenuItem(moduleType);
|
AddFlyoutMenuItem(moduleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user