mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-07 04:46:56 +01:00
Compare commits
2 Commits
dev/mjolle
...
shawn/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12e9832dd0 | ||
|
|
da176a6fc8 |
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -141,7 +141,7 @@ bla
|
||||
BLACKFRAME
|
||||
BLENDFUNCTION
|
||||
Blockquotes
|
||||
blt
|
||||
Blt
|
||||
BLURBEHIND
|
||||
BLURREGION
|
||||
bmi
|
||||
@@ -1873,7 +1873,6 @@ UPDATENOW
|
||||
UPDATEREGISTRY
|
||||
updown
|
||||
UPGRADINGPRODUCTCODE
|
||||
upscaling
|
||||
Uptool
|
||||
urld
|
||||
Usb
|
||||
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -5,7 +5,6 @@
|
||||
## PR Checklist
|
||||
|
||||
- [ ] Closes: #xxx
|
||||
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) -->
|
||||
- [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
|
||||
- [ ] **Tests:** Added/updated and all pass
|
||||
- [ ] **Localization:** All end-user-facing strings can be localized
|
||||
|
||||
2
.github/workflows/msstore-submissions.yml
vendored
2
.github/workflows/msstore-submissions.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup .NET 9.0
|
||||
uses: actions/setup-dotnet@v5
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "deps/expected-lite"]
|
||||
path = deps/expected-lite
|
||||
url = https://github.com/martinmoene/expected-lite.git
|
||||
[submodule "deps/cziplib"]
|
||||
path = deps/cziplib
|
||||
url = https://github.com/kuba--/zip.git
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<PropertyGroup Condition="'$(SkipCppCodeAnalysis)' == ''">
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
<CodeAnalysisRuleSet>$(MsbuildThisFileDirectory)\CppRuleSet.ruleset</CodeAnalysisRuleSet>
|
||||
<CAExcludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(CAExcludePath)</CAExcludePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- C++ source compile-specific things for all configurations -->
|
||||
@@ -35,7 +34,7 @@
|
||||
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
|
||||
<VcpkgEnabled>false</VcpkgEnabled>
|
||||
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
|
||||
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)</ExternalIncludePath>
|
||||
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath)</ExternalIncludePath>
|
||||
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
|
||||
<!-- This covers the case where a .dll exports a .lib, but doesn't have any ClCompile entries. -->
|
||||
<LinkControlFlowGuard>Guard</LinkControlFlowGuard>
|
||||
|
||||
@@ -147,18 +147,6 @@ _If you want to find diagnostic data events in the source code, these two links
|
||||
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent</td>
|
||||
<td>Triggered when Advanced Paste leverages the Semantic Kernel.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelErrorEvent</td>
|
||||
<td>Occurs when the Semantic Kernel workflow encounters an error.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.AdvancedPasteEndpointUsageEvent</td>
|
||||
<td>Logs the AI provider, model, and processing duration for each endpoint call.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Microsoft.PowerToys.AdvancedPasteCustomActionErrorEvent</td>
|
||||
<td>Records provider, model, and status details when a custom action fails.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Always on Top
|
||||
|
||||
40
README.md
40
README.md
@@ -7,9 +7,7 @@
|
||||
<h1 align="center">
|
||||
<span>Microsoft PowerToys</span>
|
||||
</h1>
|
||||
<p align="center">
|
||||
<span align="center">Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.</span>
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
<a href="#-installation">Installation</a>
|
||||
<span> · </span>
|
||||
@@ -20,10 +18,8 @@
|
||||
<a href="#-whats-new">Release notes</a>
|
||||
</h3>
|
||||
<br/><br/>
|
||||
|
||||
## 🔨 Utilities
|
||||
|
||||
PowerToys includes over 25 utilities to help you customize and optimize your Windows experience:
|
||||
Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
|
||||
<br/><br/>
|
||||
|
||||
| | | |
|
||||
|---|---|---|
|
||||
@@ -41,13 +37,20 @@ PowerToys includes over 25 utilities to help you customize and optimize your Win
|
||||
|
||||
## 📋 Installation
|
||||
|
||||
For detailed installation instructions and system requirements, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
|
||||
For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
|
||||
|
||||
Before you begin, make sure your device meets the system requirements:
|
||||
|
||||
> [!NOTE]
|
||||
> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer
|
||||
> - 64-bit processor: x64 or ARM64
|
||||
> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup
|
||||
|
||||
Choose one of the installation methods below:
|
||||
|
||||
But to get started quickly, choose one of the installation methods below:
|
||||
<br/><br/>
|
||||
<details open>
|
||||
<summary><strong>Download .exe from GitHub</strong></summary>
|
||||
<br/>
|
||||
<summary>Download .exe from GitHub</summary>
|
||||
|
||||
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
@@ -64,11 +67,11 @@ Go to the [PowerToys GitHub releases][github-release-link], click Assets to reve
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Microsoft Store</strong></summary>
|
||||
<br/>
|
||||
<summary>Microsoft Store</summary>
|
||||
You can easily install PowerToys from the Microsoft Store:
|
||||
<p>
|
||||
<a style="text-decoration:none" href="https://aka.ms/getPowertoys">
|
||||
@@ -79,9 +82,10 @@ You can easily install PowerToys from the Microsoft Store:
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><strong>WinGet</strong></summary>
|
||||
<br/>
|
||||
<summary>WinGet</summary>
|
||||
|
||||
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
|
||||
|
||||
*User scope installer [default]*
|
||||
@@ -96,8 +100,8 @@ winget install --scope machine Microsoft.PowerToys -s winget
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Other methods</strong></summary>
|
||||
<br/>
|
||||
<summary>Other methods</summary>
|
||||
|
||||
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
|
||||
</details>
|
||||
|
||||
|
||||
1
deps/cziplib
vendored
Submodule
1
deps/cziplib
vendored
Submodule
Submodule deps/cziplib added at 81314fff0a
@@ -4,7 +4,6 @@
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
#include "../../src/common/logger/logger.h"
|
||||
#include "../../src/common/utils/gpo.h"
|
||||
@@ -857,69 +856,14 @@ UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall)
|
||||
|
||||
try
|
||||
{
|
||||
winrt::Windows::Security::Credentials::PasswordVault vault;
|
||||
winrt::Windows::Security::Credentials::PasswordCredential cred;
|
||||
|
||||
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
winrt::Windows::Security::Credentials::PasswordVault vault;
|
||||
|
||||
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 (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey");
|
||||
vault.Remove(cred);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -205,12 +205,4 @@ internal sealed class FoundryClient
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnsureRunning()
|
||||
{
|
||||
if (!_foundryManager.IsServiceRunning)
|
||||
{
|
||||
await _foundryManager.StartServiceAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace LanguageModelProvider;
|
||||
public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
private IEnumerable<ModelDetails>? _downloadedModels;
|
||||
private FoundryClient? _foundryClient;
|
||||
private FoundryClient? _foundryManager;
|
||||
private string? _serviceUrl;
|
||||
|
||||
public static FoundryLocalModelProvider Instance { get; } = new();
|
||||
@@ -22,11 +22,13 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
|
||||
public string ProviderDescription => "The model will run locally via Foundry Local";
|
||||
|
||||
public IChatClient? GetIChatClient(string modelId)
|
||||
public string UrlPrefix => "fl://";
|
||||
|
||||
public IChatClient? GetIChatClient(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}");
|
||||
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {url}");
|
||||
InitializeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -35,22 +37,26 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryClient == 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))
|
||||
{
|
||||
Logger.LogError("[FoundryLocal] Model ID is empty after extraction");
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.LogInfo($"[FoundryLocal] Extracted model ID: {modelId}");
|
||||
|
||||
// Ensure the model is loaded before returning chat client
|
||||
try
|
||||
{
|
||||
var isLoaded = _foundryClient.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
var isLoaded = _foundryManager.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
if (!isLoaded)
|
||||
{
|
||||
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
|
||||
@@ -66,7 +72,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
}
|
||||
|
||||
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
|
||||
var baseUri = _foundryClient.GetServiceUri();
|
||||
var baseUri = _foundryManager.GetServiceUri();
|
||||
if (baseUri == null)
|
||||
{
|
||||
Logger.LogError("[FoundryLocal] Service URI is null");
|
||||
@@ -127,25 +133,24 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
|
||||
private async Task InitializeAsync(CancellationToken cancelationToken = default)
|
||||
{
|
||||
if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any())
|
||||
if (_foundryManager != null && _downloadedModels != null && _downloadedModels.Any())
|
||||
{
|
||||
await _foundryClient.EnsureRunning().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo("[FoundryLocal] Initializing provider");
|
||||
_foundryClient ??= await FoundryClient.CreateAsync();
|
||||
_foundryManager ??= await FoundryClient.CreateAsync();
|
||||
|
||||
if (_foundryClient == null)
|
||||
if (_foundryManager == null)
|
||||
{
|
||||
Logger.LogError("[FoundryLocal] Failed to create Foundry client");
|
||||
return;
|
||||
}
|
||||
|
||||
_serviceUrl ??= await _foundryClient.GetServiceUrl();
|
||||
_serviceUrl ??= await _foundryManager.GetServiceUrl();
|
||||
Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
|
||||
|
||||
var cachedModels = await _foundryClient.ListCachedModels();
|
||||
var cachedModels = await _foundryManager.ListCachedModels();
|
||||
Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models");
|
||||
|
||||
List<ModelDetails> downloadedModels = [];
|
||||
@@ -157,7 +162,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
Id = $"fl-{model.Name}",
|
||||
Name = model.Name,
|
||||
Url = $"fl://{model.Name}",
|
||||
Url = $"{UrlPrefix}{model.Name}",
|
||||
Description = $"{model.Name} running locally with Foundry Local",
|
||||
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
||||
SupportedOnQualcomm = true,
|
||||
@@ -173,7 +178,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
Logger.LogInfo("[FoundryLocal] Checking availability");
|
||||
await InitializeAsync();
|
||||
var available = _foundryClient != null;
|
||||
var available = _foundryManager != null;
|
||||
Logger.LogInfo($"[FoundryLocal] Available: {available}");
|
||||
return available;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ public interface ILanguageModelProvider
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string UrlPrefix { get; }
|
||||
|
||||
string ProviderDescription { get; }
|
||||
|
||||
Task<IEnumerable<ModelDetails>> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default);
|
||||
|
||||
IChatClient? GetIChatClient(string modelId);
|
||||
IChatClient? GetIChatClient(string url);
|
||||
|
||||
string GetIChatClientString(string url);
|
||||
}
|
||||
|
||||
106
src/common/LanguageModelProvider/LanguageModelService.cs
Normal file
106
src/common/LanguageModelProvider/LanguageModelService.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,6 @@ internal sealed class IntegrationTestUserSettings : IUserSettings
|
||||
|
||||
public bool CloseAfterLosingFocus => false;
|
||||
|
||||
public bool EnableClipboardPreview => true;
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions => _customActions;
|
||||
|
||||
public IReadOnlyList<PasteFormats> AdditionalActions => _additionalActions;
|
||||
|
||||
@@ -611,10 +611,10 @@
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
Visibility="{x:Bind IsLocalModel, Mode=OneWay}">
|
||||
<TextBlock
|
||||
x:Uid="LocalModelBadge"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="10"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Local" />
|
||||
</Border>
|
||||
<!--<Border
|
||||
Grid.Column="2"
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="20"
|
||||
Visibility="{x:Bind ViewModel.ShowClipboardPreview, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
Visibility="{x:Bind ViewModel.ClipboardHasData, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -168,8 +168,7 @@
|
||||
Margin="0,0,4,0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.ShowClipboardHistoryButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="ClipboardHistoryButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
@@ -264,17 +263,16 @@
|
||||
<Button
|
||||
Padding="0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.HasLegalLinks, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<FontIcon FontSize="12" Glyph="" />
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run x:Uid="AIMistakeNote" /><LineBreak /><Run
|
||||
x:Uid="CustomEndpointWarning"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="You are using a custom endpoint. Verify all answers." />
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<HyperlinkButton
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace AdvancedPaste.Helpers
|
||||
PromptTokens = semanticKernelFormatEvent.PromptTokens;
|
||||
CompletionTokens = semanticKernelFormatEvent.CompletionTokens;
|
||||
ModelName = semanticKernelFormatEvent.ModelName;
|
||||
ProviderType = semanticKernelFormatEvent.ProviderType;
|
||||
ActionChain = semanticKernelFormatEvent.ActionChain;
|
||||
}
|
||||
|
||||
@@ -39,8 +38,6 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
public string ModelName { get; set; }
|
||||
|
||||
public string ProviderType { get; set; }
|
||||
|
||||
public string ActionChain { get; set; }
|
||||
|
||||
public string ToJsonString() => JsonSerializer.Serialize(this, SourceGenerationContext.Default.AIServiceFormatEvent);
|
||||
|
||||
@@ -19,8 +19,6 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public bool EnableClipboardPreview { get; }
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions { get; }
|
||||
|
||||
public IReadOnlyList<PasteFormats> AdditionalActions { get; }
|
||||
|
||||
@@ -40,8 +40,6 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
|
||||
public bool EnableClipboardPreview { get; private set; }
|
||||
|
||||
public IReadOnlyList<PasteFormats> AdditionalActions => _additionalActions;
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions => _customActions;
|
||||
@@ -55,7 +53,6 @@ namespace AdvancedPaste.Settings
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new PasteAIConfiguration();
|
||||
_additionalActions = [];
|
||||
_customActions = [];
|
||||
@@ -110,7 +107,6 @@ namespace AdvancedPaste.Settings
|
||||
IsAIEnabled = properties.IsAIEnabled;
|
||||
ShowCustomPreview = properties.ShowCustomPreview;
|
||||
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
|
||||
EnableClipboardPreview = properties.EnableClipboardPreview;
|
||||
PasteAIConfiguration = properties.PasteAIConfiguration ?? new PasteAIConfiguration();
|
||||
|
||||
var sourceAdditionalActions = properties.AdditionalActions;
|
||||
@@ -167,134 +163,28 @@ namespace AdvancedPaste.Settings
|
||||
return false;
|
||||
}
|
||||
|
||||
var properties = settings.Properties;
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (legacyCredential is null)
|
||||
if (settings.Properties.IsAIEnabled || !LegacyOpenAIKeyExists())
|
||||
{
|
||||
return legacyAdvancedAIConsumed;
|
||||
return false;
|
||||
}
|
||||
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = new PasteAIConfiguration();
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
|
||||
configurationUpdated = true;
|
||||
}
|
||||
|
||||
if (openAIProvider is not null)
|
||||
{
|
||||
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
const bool shouldEnableAI = true;
|
||||
bool enabledUpdated = false;
|
||||
if (properties.IsAIEnabled != shouldEnableAI)
|
||||
{
|
||||
properties.IsAIEnabled = shouldEnableAI;
|
||||
enabledUpdated = true;
|
||||
}
|
||||
|
||||
return configurationUpdated || enabledUpdated || legacyAdvancedAIConsumed;
|
||||
settings.Properties.IsAIEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PasswordCredential TryGetLegacyOpenAICredential()
|
||||
private static bool LegacyOpenAIKeyExists()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
credential?.RetrievePassword();
|
||||
return credential;
|
||||
return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void StoreMigratedOpenAICredential(string providerId, string serviceType, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var serviceKind = serviceType.ToAIServiceType();
|
||||
if (serviceKind != AIServiceType.OpenAI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string resource = "https://platform.openai.com/api-keys";
|
||||
string username = $"PowerToys_AdvancedPaste_PasteAI_openai_{NormalizeProviderIdentifier(providerId)}";
|
||||
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, resource, username);
|
||||
|
||||
PasswordCredential credential = new(resource, username, password);
|
||||
vault.Add(credential);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to migrate legacy OpenAI credential", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryRemoveCredential(PasswordVault vault, string credentialResource, string credentialUserName)
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
|
||||
vault.Remove(existingCred);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Credential doesn't exist, which is fine
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeProviderIdentifier(string providerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
|
||||
return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public async Task SetActiveAIProviderAsync(string providerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
|
||||
@@ -83,39 +83,32 @@ namespace AdvancedPaste.Services.CustomActions
|
||||
SystemPrompt = systemPrompt,
|
||||
};
|
||||
|
||||
var operationStart = DateTime.UtcNow;
|
||||
|
||||
var providerContent = await provider.ProcessPasteAsync(
|
||||
request,
|
||||
cancellationToken,
|
||||
progress);
|
||||
|
||||
var durationMs = (int)Math.Round((DateTime.UtcNow - operationStart).TotalMilliseconds);
|
||||
|
||||
var usage = request.Usage;
|
||||
var content = providerContent ?? string.Empty;
|
||||
|
||||
// Log endpoint usage (custom action pipeline is not the advanced SK flow)
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType, providerConfig.Model ?? string.Empty, isAdvanced: false, durationMs: durationMs);
|
||||
// Log endpoint usage
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType);
|
||||
PowerToysTelemetry.Log.WriteEvent(endpointEvent);
|
||||
|
||||
Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}, DurationMs={durationMs}");
|
||||
Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}");
|
||||
|
||||
return new CustomActionTransformResult(content, usage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} failed", ex);
|
||||
var statusCode = ExtractStatusCode(ex);
|
||||
var modelName = providerConfig.Model ?? string.Empty;
|
||||
AdvancedPasteCustomActionErrorEvent errorEvent = new(providerConfig.ProviderType, modelName, statusCode, ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
|
||||
PowerToysTelemetry.Log.WriteEvent(errorEvent);
|
||||
|
||||
if (ex is PasteActionException or OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
var statusCode = ExtractStatusCode(ex);
|
||||
var failureMessage = providerConfig.ProviderType switch
|
||||
{
|
||||
AIServiceType.OpenAI or AIServiceType.AzureOpenAI => ErrorHelpers.TranslateErrorText(statusCode),
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
|
||||
public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new FoundryLocalPasteProvider(config));
|
||||
|
||||
private static readonly FoundryLocalModelProvider _modelProvider = FoundryLocalModelProvider.Instance;
|
||||
private static readonly LanguageModelService LanguageModels = LanguageModelService.CreateDefault();
|
||||
|
||||
private readonly PasteAIConfig _config;
|
||||
|
||||
@@ -72,11 +72,11 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
throw new PasteActionException(
|
||||
"No Foundry Local model selected",
|
||||
new InvalidOperationException("Model identifier is required"),
|
||||
aiServiceMessage: "Please select a model in the AI provider settings.");
|
||||
aiServiceMessage: "Please select a model in the AI provider settings. Model identifier should be in the format 'fl://model-name'.");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var chatClient = _modelProvider.GetIChatClient(modelReference);
|
||||
var chatClient = LanguageModels.GetClient(modelReference);
|
||||
if (chatClient is null)
|
||||
{
|
||||
throw new PasteActionException(
|
||||
@@ -85,6 +85,9 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
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)
|
||||
var actualModelId = modelReference.Replace("fl://", string.Empty).Trim('/');
|
||||
|
||||
var userMessageContent = $"""
|
||||
User instructions:
|
||||
{prompt}
|
||||
@@ -101,7 +104,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
new(ChatRole.User, userMessageContent),
|
||||
};
|
||||
|
||||
var chatOptions = CreateChatOptions(_config?.SystemPrompt, modelReference);
|
||||
var chatOptions = CreateChatOptions(_config?.SystemPrompt, actualModelId);
|
||||
|
||||
progress?.Report(0.1);
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ public abstract class KernelServiceBase(
|
||||
ICustomActionTransformService customActionTransformService) : IKernelService
|
||||
{
|
||||
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 IPromptModerationService _promptModerationService = promptModerationService;
|
||||
@@ -145,8 +144,7 @@ public abstract class KernelServiceBase(
|
||||
|
||||
ChatHistory chatHistory = [];
|
||||
|
||||
var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
|
||||
chatHistory.AddSystemMessage(systemPrompt);
|
||||
chatHistory.AddSystemMessage(runtimeConfig.SystemPrompt);
|
||||
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
||||
chatHistory.AddUserMessage(prompt);
|
||||
|
||||
@@ -188,20 +186,12 @@ public abstract class KernelServiceBase(
|
||||
|
||||
private void LogResult(bool cacheUsed, bool isSavedQuery, IEnumerable<ActionChainItem> actionChain, AIServiceUsage usage)
|
||||
{
|
||||
var runtimeConfig = GetRuntimeConfiguration();
|
||||
|
||||
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(
|
||||
cacheUsed,
|
||||
isSavedQuery,
|
||||
usage.PromptTokens,
|
||||
usage.CompletionTokens,
|
||||
AdvancedAIModelName,
|
||||
runtimeConfig.ServiceType.ToString(),
|
||||
AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
|
||||
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(cacheUsed, isSavedQuery, usage.PromptTokens, usage.CompletionTokens, AdvancedAIModelName, AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
|
||||
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||
|
||||
// Log endpoint usage
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType, AdvancedAIModelName, isAdvanced: true);
|
||||
var runtimeConfig = GetRuntimeConfiguration();
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType);
|
||||
PowerToysTelemetry.Log.WriteEvent(endpointEvent);
|
||||
|
||||
var logEvent = new AIServiceFormatEvent(telemetryEvent);
|
||||
|
||||
@@ -359,13 +359,6 @@
|
||||
</data>
|
||||
<data name="Relative_Date_TimeFormat" xml:space="preserve">
|
||||
<value>{0}, {1}</value>
|
||||
<comment>(e.g., "10/20/2025, 17:05" in the user's locale)</comment>
|
||||
</data>
|
||||
<data name="CustomEndpointWarning" xml:space="preserve">
|
||||
<value>You are using a custom endpoint. Verify all answers.</value>
|
||||
</data>
|
||||
<data name="LocalModelBadge" xml:space="preserve">
|
||||
<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>(e.g., “10/20/2025, 17:05” in the user’s locale)</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,34 +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.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace AdvancedPaste.Telemetry;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public sealed class AdvancedPasteCustomActionErrorEvent : EventBase, IEvent
|
||||
{
|
||||
public AdvancedPasteCustomActionErrorEvent(AIServiceType providerType, string modelName, int statusCode, string error)
|
||||
{
|
||||
ProviderType = providerType.ToString();
|
||||
ModelName = modelName;
|
||||
StatusCode = statusCode;
|
||||
Error = error;
|
||||
}
|
||||
|
||||
public string ProviderType { get; set; }
|
||||
|
||||
public string ModelName { get; set; }
|
||||
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
public string Error { get; set; }
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -19,27 +19,9 @@ public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
|
||||
/// </summary>
|
||||
public string ProviderType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configured model name.
|
||||
/// </summary>
|
||||
public string ModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the advanced AI pipeline was used.
|
||||
/// </summary>
|
||||
public bool IsAdvanced { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the total duration in milliseconds, or -1 if unavailable.
|
||||
/// </summary>
|
||||
public int DurationMs { get; set; }
|
||||
|
||||
public AdvancedPasteEndpointUsageEvent(AIServiceType providerType, string modelName, bool isAdvanced, int durationMs = -1)
|
||||
public AdvancedPasteEndpointUsageEvent(AIServiceType providerType)
|
||||
{
|
||||
ProviderType = providerType.ToString();
|
||||
ModelName = modelName;
|
||||
IsAdvanced = isAdvanced;
|
||||
DurationMs = durationMs;
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace AdvancedPaste.Telemetry;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string providerType, string actionChain) : EventBase, IEvent
|
||||
public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string actionChain) : EventBase, IEvent
|
||||
{
|
||||
public static string FormatActionChain(IEnumerable<ActionChainItem> actionChain) => FormatActionChain(actionChain.Select(item => item.Format));
|
||||
|
||||
@@ -30,8 +30,6 @@ public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSaved
|
||||
|
||||
public string ModelName { get; set; } = modelName;
|
||||
|
||||
public string ProviderType { get; set; } = providerType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a comma-separated list of paste formats used - in the same order they were executed.
|
||||
/// Conceptually an array but formatted this way to work around https://github.com/dotnet/runtime/issues/10428
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace AdvancedPaste.ViewModels
|
||||
private ClipboardFormat _availableClipboardFormats;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowClipboardHistoryButton))]
|
||||
private bool _clipboardHistoryEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -77,7 +76,6 @@ namespace AdvancedPaste.ViewModels
|
||||
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
|
||||
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
|
||||
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegalLinks))]
|
||||
private bool _isAllowedByGPO;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -223,16 +221,10 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
|
||||
|
||||
public bool HasLegalLinks => HasTermsLink || HasPrivacyLink;
|
||||
|
||||
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
||||
|
||||
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
||||
|
||||
public bool ShowClipboardPreview => _userSettings.EnableClipboardPreview;
|
||||
|
||||
public bool ShowClipboardHistoryButton => ClipboardHistoryEnabled;
|
||||
|
||||
public bool HasIndeterminateTransformProgress => double.IsNaN(TransformProgress);
|
||||
|
||||
private PasteFormats CustomAIFormat =>
|
||||
@@ -318,7 +310,6 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
||||
OnPropertyChanged(nameof(AIProviders));
|
||||
OnPropertyChanged(nameof(AllowedAIProviders));
|
||||
OnPropertyChanged(nameof(ShowClipboardPreview));
|
||||
|
||||
NotifyActiveProviderChanged();
|
||||
|
||||
@@ -370,7 +361,6 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(PrivacyLinkUri));
|
||||
OnPropertyChanged(nameof(HasTermsLink));
|
||||
OnPropertyChanged(nameof(HasPrivacyLink));
|
||||
OnPropertyChanged(nameof(HasLegalLinks));
|
||||
}
|
||||
|
||||
private void RefreshPasteFormats()
|
||||
|
||||
@@ -196,10 +196,10 @@ public:
|
||||
m_enabled = true;
|
||||
Trace::EnableCursorWrap(true);
|
||||
|
||||
// Always start the mouse hook when the module is enabled
|
||||
// This ensures cursor wrapping is active immediately after enabling
|
||||
StartMouseHook();
|
||||
Logger::info("CursorWrap enabled - mouse hook started");
|
||||
if (m_autoActivate)
|
||||
{
|
||||
StartMouseHook();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
@@ -208,7 +208,6 @@ public:
|
||||
m_enabled = false;
|
||||
Trace::EnableCursorWrap(false);
|
||||
StopMouseHook();
|
||||
Logger::info("CursorWrap disabled - mouse hook stopped");
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
|
||||
@@ -1812,7 +1812,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
|
||||
// Check if GIF is selected by comparing the text
|
||||
bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
|
||||
|
||||
// If GIF is selected, set the scaling to the g_RecordScalingGIF value; otherwise to the g_RecordScalingMP4 value
|
||||
// if gif is selected set the scaling to the g_recordScaleGIF value otherwise to the g_recordScaleMP4 value
|
||||
if (isGifSelected) {
|
||||
g_RecordScaling = g_RecordScalingGIF;
|
||||
|
||||
@@ -6440,10 +6440,12 @@ LRESULT APIENTRY MainWndProc(
|
||||
GetCursorPos(&local_savedCursorPos);
|
||||
}
|
||||
|
||||
// Determine the user's desired save area in zoomed viewport coordinates.
|
||||
// This will be the entire viewport if the user does not select a crop
|
||||
// rectangle.
|
||||
int copyX = 0, copyY = 0, copyWidth = width, copyHeight = height;
|
||||
HBITMAP hInterimSaveBitmap;
|
||||
HDC hInterimSaveDc;
|
||||
HBITMAP hSaveBitmap;
|
||||
HDC hSaveDc;
|
||||
int copyX, copyY;
|
||||
int copyWidth, copyHeight;
|
||||
|
||||
if ( LOWORD( wParam ) == IDC_SAVE_CROP )
|
||||
{
|
||||
@@ -6458,51 +6460,55 @@ LRESULT APIENTRY MainWndProc(
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto copyRc = selectRectangle.SelectedRect();
|
||||
selectRectangle.Stop();
|
||||
g_RecordCropping = FALSE;
|
||||
|
||||
copyX = copyRc.left;
|
||||
copyY = copyRc.top;
|
||||
copyWidth = copyRc.right - copyRc.left;
|
||||
copyHeight = copyRc.bottom - copyRc.top;
|
||||
}
|
||||
else
|
||||
{
|
||||
copyX = 0;
|
||||
copyY = 0;
|
||||
copyWidth = width;
|
||||
copyHeight = height;
|
||||
}
|
||||
OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight );
|
||||
|
||||
RECT oldClipRect{};
|
||||
GetClipCursor( &oldClipRect );
|
||||
ClipCursor( NULL );
|
||||
|
||||
// Translate the viewport selection into coordinates for the 1:1 source
|
||||
// bitmap hdcScreenCompat.
|
||||
int viewportX, viewportY;
|
||||
GetZoomedTopLeftCoordinates(
|
||||
zoomLevel, &cursorPos, &viewportX, width, &viewportY, height );
|
||||
// Capture the screen before displaying the save dialog
|
||||
hInterimSaveDc = CreateCompatibleDC( hdcScreen );
|
||||
hInterimSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight );
|
||||
SelectObject( hInterimSaveDc, hInterimSaveBitmap );
|
||||
|
||||
int saveX = viewportX + static_cast<int>( copyX / zoomLevel );
|
||||
int saveY = viewportY + static_cast<int>( copyY / zoomLevel );
|
||||
int saveWidth = static_cast<int>( copyWidth / zoomLevel );
|
||||
int saveHeight = static_cast<int>( copyHeight / zoomLevel );
|
||||
hSaveDc = CreateCompatibleDC( hdcScreen );
|
||||
#if SCALE_HALFTONE
|
||||
SetStretchBltMode( hInterimSaveDc, HALFTONE );
|
||||
SetStretchBltMode( hSaveDc, HALFTONE );
|
||||
#else
|
||||
// Use HALFTONE for better quality when smooth image is enabled
|
||||
if (g_SmoothImage) {
|
||||
SetStretchBltMode( hInterimSaveDc, HALFTONE );
|
||||
SetStretchBltMode( hSaveDc, HALFTONE );
|
||||
} else {
|
||||
SetStretchBltMode( hInterimSaveDc, COLORONCOLOR );
|
||||
SetStretchBltMode( hSaveDc, COLORONCOLOR );
|
||||
}
|
||||
#endif
|
||||
StretchBlt( hInterimSaveDc,
|
||||
0, 0,
|
||||
copyWidth, copyHeight,
|
||||
hdcScreen,
|
||||
monInfo.rcMonitor.left + copyX,
|
||||
monInfo.rcMonitor.top + copyY,
|
||||
copyWidth, copyHeight,
|
||||
SRCCOPY|CAPTUREBLT );
|
||||
|
||||
// Create a pixel-accurate copy of the desired area from the source bitmap.
|
||||
wil::unique_hdc hdcActualSize( CreateCompatibleDC( hdcScreen ) );
|
||||
wil::unique_hbitmap hbmActualSize(
|
||||
CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight ) );
|
||||
// Note: we do not need to restore the existing context later. The objects
|
||||
// are transient and not reused.
|
||||
SelectObject( hdcActualSize.get(), hbmActualSize.get() );
|
||||
|
||||
// Perform a direct 1:1 copy from the backing bitmap.
|
||||
BitBlt( hdcActualSize.get(),
|
||||
0, 0,
|
||||
saveWidth, saveHeight,
|
||||
hdcScreenCompat,
|
||||
saveX, saveY,
|
||||
SRCCOPY | CAPTUREBLT );
|
||||
|
||||
// Open the Save As dialog and capture the desired file path and whether to
|
||||
// save the zoomed display or the source bitmap pixels.
|
||||
g_bSaveInProgress = true;
|
||||
memset( &openFileName, 0, sizeof(openFileName ));
|
||||
openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400;
|
||||
@@ -6518,7 +6524,6 @@ LRESULT APIENTRY MainWndProc(
|
||||
"Actual size PNG\0*.png\0\0";
|
||||
//"Actual size BMP\0*.bmp\0\0";
|
||||
openFileName.lpstrFile = filePath;
|
||||
|
||||
if( GetSaveFileName( &openFileName ) )
|
||||
{
|
||||
TCHAR targetFilePath[MAX_PATH];
|
||||
@@ -6528,47 +6533,42 @@ LRESULT APIENTRY MainWndProc(
|
||||
_tcscat( targetFilePath, L".png" );
|
||||
}
|
||||
|
||||
if( openFileName.nFilterIndex == 2 )
|
||||
// Save image at screen size
|
||||
if( openFileName.nFilterIndex == 1 )
|
||||
{
|
||||
// Save at actual size.
|
||||
SavePng( targetFilePath, hbmActualSize.get() );
|
||||
SavePng( targetFilePath, hInterimSaveBitmap );
|
||||
}
|
||||
// Save image scaled down to actual size
|
||||
else
|
||||
{
|
||||
// Save zoomed-in image at screen resolution.
|
||||
#if SCALE_HALFTONE
|
||||
const int bltMode = HALFTONE;
|
||||
#else
|
||||
// Use HALFTONE for better quality when smooth image is enabled
|
||||
const int bltMode = g_SmoothImage ? HALFTONE : COLORONCOLOR;
|
||||
#endif
|
||||
// Recreate the zoomed-in view by upscaling from our source bitmap.
|
||||
wil::unique_hdc hdcZoomed( CreateCompatibleDC(hdcScreen) );
|
||||
wil::unique_hbitmap hbmZoomed(
|
||||
CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight ) );
|
||||
SelectObject( hdcZoomed.get(), hbmZoomed.get() );
|
||||
int saveWidth = static_cast<int>( copyWidth / zoomLevel );
|
||||
int saveHeight = static_cast<int>( copyHeight / zoomLevel );
|
||||
|
||||
SetStretchBltMode( hdcZoomed.get(), bltMode );
|
||||
hSaveBitmap = CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight );
|
||||
SelectObject( hSaveDc, hSaveBitmap );
|
||||
|
||||
StretchBlt( hdcZoomed.get(),
|
||||
0, 0,
|
||||
copyWidth, copyHeight,
|
||||
hdcActualSize.get(),
|
||||
StretchBlt( hSaveDc,
|
||||
0, 0,
|
||||
saveWidth, saveHeight,
|
||||
hInterimSaveDc,
|
||||
0,
|
||||
0,
|
||||
copyWidth, copyHeight,
|
||||
SRCCOPY | CAPTUREBLT );
|
||||
|
||||
SavePng( targetFilePath, hbmZoomed.get() );
|
||||
SavePng( targetFilePath, hSaveBitmap );
|
||||
}
|
||||
}
|
||||
g_bSaveInProgress = false;
|
||||
|
||||
DeleteDC( hInterimSaveDc );
|
||||
DeleteDC( hSaveDc );
|
||||
|
||||
if( lParam != SHALLOW_ZOOM )
|
||||
{
|
||||
SetCursorPos( local_savedCursorPos.x, local_savedCursorPos.y );
|
||||
SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y);
|
||||
}
|
||||
ClipCursor( &oldClipRect );
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.MainPage;
|
||||
|
||||
@@ -37,10 +38,8 @@ public partial class MainListPage : DynamicListPage,
|
||||
"com.microsoft.cmdpal.builtin.datetime",
|
||||
];
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly TopLevelCommandManager _tlcManager;
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly AliasManager _aliasManager;
|
||||
private readonly AppStateModel _appState;
|
||||
private List<Scored<IListItem>>? _filteredItems;
|
||||
private List<Scored<IListItem>>? _filteredApps;
|
||||
private List<Scored<IListItem>>? _fallbackItems;
|
||||
@@ -54,18 +53,17 @@ public partial class MainListPage : DynamicListPage,
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public MainListPage(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel, AliasManager aliasManager, AppStateModel appStateModel)
|
||||
public MainListPage(IServiceProvider serviceProvider)
|
||||
{
|
||||
Title = Resources.builtin_home_name;
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png");
|
||||
PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
_tlcManager = topLevelCommandManager;
|
||||
_tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
_tlcManager.PropertyChanged += TlcManager_PropertyChanged;
|
||||
_tlcManager.TopLevelCommands.CollectionChanged += Commands_CollectionChanged;
|
||||
|
||||
_appState = appStateModel;
|
||||
|
||||
// The all apps page will kick off a BG thread to start loading apps.
|
||||
// We just want to know when it is done.
|
||||
var allApps = AllAppsCommandProvider.Page;
|
||||
@@ -80,13 +78,12 @@ public partial class MainListPage : DynamicListPage,
|
||||
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateFallbackItemsMessage>(this);
|
||||
|
||||
_settings = settingsModel;
|
||||
_settings.SettingsChanged += SettingsChangedHandler;
|
||||
HotReloadSettings(_settings);
|
||||
var settings = _serviceProvider.GetService<SettingsModel>()!;
|
||||
settings.SettingsChanged += SettingsChangedHandler;
|
||||
HotReloadSettings(settings);
|
||||
_includeApps = _tlcManager.IsProviderActive(AllAppsCommandProvider.WellKnownId);
|
||||
|
||||
IsLoading = true;
|
||||
_aliasManager = aliasManager;
|
||||
}
|
||||
|
||||
private void TlcManager_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
@@ -222,12 +219,14 @@ public partial class MainListPage : DynamicListPage,
|
||||
// Handle changes to the filter text here
|
||||
if (!string.IsNullOrEmpty(SearchText))
|
||||
{
|
||||
var aliases = _serviceProvider.GetService<AliasManager>()!;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_aliasManager.CheckAlias(newSearch))
|
||||
if (aliases.CheckAlias(newSearch))
|
||||
{
|
||||
if (_filteredItemsIncludesApps != _includeApps)
|
||||
{
|
||||
@@ -389,7 +388,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
}
|
||||
}
|
||||
|
||||
var history = _appState.RecentCommands!;
|
||||
var history = _serviceProvider.GetService<AppStateModel>()!.RecentCommands!;
|
||||
Func<string, IListItem, int> scoreItem = (a, b) => { return ScoreTopLevelItem(a, b, history); };
|
||||
|
||||
// Produce a list of everything that matches the current filter.
|
||||
@@ -484,8 +483,9 @@ public partial class MainListPage : DynamicListPage,
|
||||
|
||||
private bool ActuallyLoading()
|
||||
{
|
||||
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
var allApps = AllAppsCommandProvider.Page;
|
||||
return allApps.IsLoading || _tlcManager.IsLoading;
|
||||
return allApps.IsLoading || tlcManager.IsLoading;
|
||||
}
|
||||
|
||||
// Almost verbatim ListHelpers.ScoreListItem, but also accounting for the
|
||||
@@ -580,9 +580,10 @@ public partial class MainListPage : DynamicListPage,
|
||||
public void UpdateHistory(IListItem topLevelOrAppItem)
|
||||
{
|
||||
var id = IdForTopLevelOrAppItem(topLevelOrAppItem);
|
||||
var history = _appState.RecentCommands;
|
||||
var state = _serviceProvider.GetService<AppStateModel>()!;
|
||||
var history = state.RecentCommands;
|
||||
history.AddHistoryItem(id);
|
||||
AppStateModel.SaveState(_appState);
|
||||
AppStateModel.SaveState(state);
|
||||
}
|
||||
|
||||
private static string IdForTopLevelOrAppItem(IListItem topLevelOrAppItem)
|
||||
@@ -614,9 +615,10 @@ public partial class MainListPage : DynamicListPage,
|
||||
_tlcManager.PropertyChanged -= TlcManager_PropertyChanged;
|
||||
_tlcManager.TopLevelCommands.CollectionChanged -= Commands_CollectionChanged;
|
||||
|
||||
if (_settings is not null)
|
||||
var settings = _serviceProvider.GetService<SettingsModel>();
|
||||
if (settings is not null)
|
||||
{
|
||||
_settings.SettingsChanged -= SettingsChangedHandler;
|
||||
settings.SettingsChanged -= SettingsChangedHandler;
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||
|
||||
@@ -9,14 +9,16 @@ using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Properties;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class ProviderSettingsViewModel(
|
||||
CommandProviderWrapper _provider,
|
||||
ProviderSettings _providerSettings,
|
||||
SettingsModel _settings) : ObservableObject
|
||||
IServiceProvider _serviceProvider) : ObservableObject
|
||||
{
|
||||
private readonly SettingsModel _settings = _serviceProvider.GetService<SettingsModel>()!;
|
||||
private readonly Lock _initializeSettingsLock = new();
|
||||
private Task? _initializeSettingsTask;
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly TopLevelCommandManager _tlcManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
@@ -141,10 +142,10 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
|
||||
public SettingsExtensionsViewModel Extensions { get; }
|
||||
|
||||
public SettingsViewModel(SettingsModel settings, TaskScheduler scheduler, TopLevelCommandManager topLevelCommandManager)
|
||||
public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler)
|
||||
{
|
||||
_settings = settings;
|
||||
_tlcManager = topLevelCommandManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
var activeProviders = GetCommandProviders();
|
||||
var allProviderSettings = _settings.ProviderSettings;
|
||||
@@ -153,7 +154,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
var providerSettings = settings.GetProviderSettings(item);
|
||||
|
||||
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _settings);
|
||||
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _serviceProvider);
|
||||
CommandProviders.Add(settingsModel);
|
||||
}
|
||||
|
||||
@@ -162,7 +163,8 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
|
||||
private IEnumerable<CommandProviderWrapper> GetCommandProviders()
|
||||
{
|
||||
var allProviders = _tlcManager.CommandProviders;
|
||||
var manager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
var allProviders = manager.CommandProviders;
|
||||
return allProviders;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
|
||||
TaskScheduler IPageContext.Scheduler => _taskScheduler;
|
||||
|
||||
public TopLevelCommandManager(IServiceProvider serviceProvider, TaskScheduler taskScheduler)
|
||||
public TopLevelCommandManager(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_taskScheduler = taskScheduler;
|
||||
_taskScheduler = _serviceProvider.GetService<TaskScheduler>()!;
|
||||
WeakReferenceMessenger.Default.Register<ReloadCommandsMessage>(this);
|
||||
_reloadCommandsGate = new(ReloadAllCommandsAsyncCore);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ using Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker;
|
||||
using Microsoft.CmdPal.Ext.WinGet;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Pages;
|
||||
using Microsoft.CmdPal.UI.Settings;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
@@ -53,7 +51,10 @@ public partial class App : Application
|
||||
|
||||
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
|
||||
|
||||
private readonly ServiceProvider _services;
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
|
||||
/// </summary>
|
||||
public IServiceProvider Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
@@ -66,7 +67,7 @@ public partial class App : Application
|
||||
_globalErrorHandler.Register(this);
|
||||
#endif
|
||||
|
||||
_services = ConfigureServices();
|
||||
Services = ConfigureServices();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
@@ -93,7 +94,7 @@ public partial class App : Application
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
AppWindow = _services.GetRequiredService<MainWindow>();
|
||||
AppWindow = new MainWindow();
|
||||
|
||||
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
|
||||
@@ -109,11 +110,6 @@ public partial class App : Application
|
||||
|
||||
// Root services
|
||||
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
|
||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
|
||||
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
||||
|
||||
// TODO: Register ILogger
|
||||
|
||||
// Built-in Commands. Order matters - this is the order they'll be presented by default.
|
||||
var allApps = new AllAppsCommandProvider();
|
||||
@@ -156,35 +152,25 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
|
||||
// Settings & state
|
||||
// Models
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
services.AddSingleton<AliasManager>();
|
||||
services.AddSingleton<HotkeyManager>();
|
||||
var sm = SettingsModel.LoadSettings();
|
||||
services.AddSingleton(sm);
|
||||
var state = AppStateModel.LoadState();
|
||||
services.AddSingleton(state);
|
||||
|
||||
// Services
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
services.AddSingleton<AliasManager>();
|
||||
services.AddSingleton<HotkeyManager>();
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
services.AddSingleton<TrayIconService>();
|
||||
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||
|
||||
// ViewModels
|
||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||
services.AddSingleton<SettingsViewModel>();
|
||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||
services.AddSingleton<ITelemetryService, TelemetryForwarder>();
|
||||
|
||||
// ViewModels
|
||||
services.AddSingleton<ShellViewModel>();
|
||||
|
||||
// Views
|
||||
services.AddSingleton<MainWindow>();
|
||||
services.AddSingleton<ShellPage>();
|
||||
|
||||
services.AddTransient<ListPage>();
|
||||
|
||||
// Settings Pages
|
||||
services.AddTransient<GeneralPage>();
|
||||
services.AddTransient<ExtensionPage>();
|
||||
services.AddTransient<ExtensionsPage>();
|
||||
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -30,7 +31,6 @@ public sealed partial class ListPage : Page,
|
||||
IRecipient<ActivateSelectedListItemMessage>,
|
||||
IRecipient<ActivateSecondaryCommandMessage>
|
||||
{
|
||||
private readonly SettingsModel _settings;
|
||||
private InputSource _lastInputSource;
|
||||
|
||||
internal ListViewModel? ViewModel
|
||||
@@ -51,11 +51,9 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
}
|
||||
|
||||
public ListPage(SettingsModel settings)
|
||||
public ListPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
_settings = settings;
|
||||
|
||||
this.NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
this.ItemView.Loaded += Items_Loaded;
|
||||
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
|
||||
@@ -135,7 +133,8 @@ public sealed partial class ListPage : Page,
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settings.SingleClickActivates)
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (settings.SingleClickActivates)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
@@ -151,7 +150,8 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
if (ItemView.SelectedItem is ListItemViewModel vm)
|
||||
{
|
||||
if (!_settings.SingleClickActivates)
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (!settings.SingleClickActivates)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(vm);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
|
||||
public sealed partial class TrayIconService
|
||||
internal sealed partial class TrayIconService
|
||||
{
|
||||
private const uint MY_NOTIFY_ID = 1000;
|
||||
private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1;
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
MinHeight="240"
|
||||
Activated="MainWindow_Activated"
|
||||
Closed="MainWindow_Closed"
|
||||
x:Name="Window"
|
||||
mc:Ignorable="d">
|
||||
<pages:ShellPage x:Name="RootShellPage" />
|
||||
</winuiex:WindowEx>
|
||||
|
||||
@@ -14,9 +14,9 @@ using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.Pages;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Composition.SystemBackdrops;
|
||||
@@ -58,10 +58,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private readonly KeyboardListener _keyboardListener;
|
||||
private readonly LocalKeyboardListener _localKeyboardListener;
|
||||
private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new();
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly TrayIconService _trayIconService;
|
||||
private readonly IExtensionService _extensionService;
|
||||
private readonly ShellPage _rootShellPage;
|
||||
private bool _ignoreHotKeyWhenFullScreen = true;
|
||||
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
@@ -69,17 +65,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private WindowPosition _currentWindowPosition = new();
|
||||
|
||||
public MainWindow(SettingsModel settingsModel, TrayIconService trayIconService, IExtensionService extensionService, ShellPage shellPage)
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_settings = settingsModel;
|
||||
_trayIconService = trayIconService;
|
||||
_extensionService = extensionService;
|
||||
|
||||
_rootShellPage = shellPage;
|
||||
Window.WindowContent = _rootShellPage;
|
||||
|
||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||
|
||||
unsafe
|
||||
@@ -113,7 +102,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
SizeChanged += WindowSizeChanged;
|
||||
_rootShellPage.Loaded += RootShellPage_Loaded;
|
||||
RootShellPage.Loaded += RootShellPage_Loaded;
|
||||
|
||||
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
|
||||
|
||||
@@ -127,10 +116,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
// Load our settings, and then also wire up a settings changed handler
|
||||
HotReloadSettings();
|
||||
_settings.SettingsChanged += SettingsChangedHandler;
|
||||
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
|
||||
|
||||
// Make sure that we update the acrylic theme when the OS theme changes
|
||||
_rootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
|
||||
RootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
|
||||
|
||||
// Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h
|
||||
NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
|
||||
@@ -171,7 +160,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void RestoreWindowPosition()
|
||||
{
|
||||
if (_settings.LastWindowPosition is not WindowPosition savedPosition)
|
||||
var settings = App.Current.Services.GetService<SettingsModel>();
|
||||
if (settings?.LastWindowPosition is not WindowPosition savedPosition)
|
||||
{
|
||||
PositionCentered();
|
||||
return;
|
||||
@@ -228,10 +218,12 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void HotReloadSettings()
|
||||
{
|
||||
SetupHotkey(_settings);
|
||||
_trayIconService.SetupTrayIcon(_settings.ShowSystemTrayIcon);
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
|
||||
_ignoreHotKeyWhenFullScreen = _settings.IgnoreShortcutWhenFullscreen;
|
||||
SetupHotkey(settings);
|
||||
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
|
||||
|
||||
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen;
|
||||
}
|
||||
|
||||
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
|
||||
@@ -386,7 +378,9 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
public void Receive(ShowWindowMessage message)
|
||||
{
|
||||
ShowHwnd(message.Hwnd, _settings.SummonOn);
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
|
||||
ShowHwnd(message.Hwnd, settings.SummonOn);
|
||||
}
|
||||
|
||||
public void Receive(HideWindowMessage message)
|
||||
@@ -473,11 +467,13 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
internal void MainWindow_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
var serviceProvider = App.Current.Services;
|
||||
UpdateWindowPositionInMemory();
|
||||
|
||||
if (_settings is not null)
|
||||
var settings = serviceProvider.GetService<SettingsModel>();
|
||||
if (settings is not null)
|
||||
{
|
||||
_settings.LastWindowPosition = new WindowPosition
|
||||
settings.LastWindowPosition = new WindowPosition
|
||||
{
|
||||
X = _currentWindowPosition.X,
|
||||
Y = _currentWindowPosition.Y,
|
||||
@@ -485,12 +481,13 @@ public sealed partial class MainWindow : WindowEx,
|
||||
Height = _currentWindowPosition.Height,
|
||||
};
|
||||
|
||||
SettingsModel.SaveSettings(_settings);
|
||||
SettingsModel.SaveSettings(settings);
|
||||
}
|
||||
|
||||
_extensionService.SignalStopExtensionsAsync();
|
||||
var extensionService = serviceProvider.GetService<IExtensionService>()!;
|
||||
extensionService.SignalStopExtensionsAsync();
|
||||
|
||||
_trayIconService.Destroy();
|
||||
App.Current.Services.GetService<TrayIconService>()!.Destroy();
|
||||
|
||||
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
|
||||
// Workaround by turning it off before shutdown.
|
||||
@@ -516,28 +513,28 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private void UpdateRegionsForCustomTitleBar()
|
||||
{
|
||||
// Specify the interactive regions of the title bar.
|
||||
var scaleAdjustment = _rootShellPage.XamlRoot.RasterizationScale;
|
||||
var scaleAdjustment = RootShellPage.XamlRoot.RasterizationScale;
|
||||
|
||||
// Get the rectangle around our XAML content. We're going to mark this
|
||||
// rectangle as "Passthrough", so that the normal window operations
|
||||
// (resizing, dragging) don't apply in this space.
|
||||
var transform = _rootShellPage.TransformToVisual(null);
|
||||
var transform = RootShellPage.TransformToVisual(null);
|
||||
|
||||
// Reserve 16px of space at the top for dragging.
|
||||
var topHeight = 16;
|
||||
var bounds = transform.TransformBounds(new Rect(
|
||||
0,
|
||||
topHeight,
|
||||
_rootShellPage.ActualWidth,
|
||||
_rootShellPage.ActualHeight));
|
||||
RootShellPage.ActualWidth,
|
||||
RootShellPage.ActualHeight));
|
||||
var contentRect = GetRect(bounds, scaleAdjustment);
|
||||
var rectArray = new RectInt32[] { contentRect };
|
||||
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
|
||||
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
|
||||
|
||||
// Add a drag-able region on top
|
||||
var w = _rootShellPage.ActualWidth;
|
||||
_ = _rootShellPage.ActualHeight;
|
||||
var w = RootShellPage.ActualWidth;
|
||||
_ = RootShellPage.ActualHeight;
|
||||
var dragSides = new RectInt32[]
|
||||
{
|
||||
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall
|
||||
@@ -627,7 +624,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
}
|
||||
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_settings.AllowExternalReload == true)
|
||||
var settings = App.Current.Services.GetService<SettingsModel>();
|
||||
if (settings?.AllowExternalReload == true)
|
||||
{
|
||||
Logger.LogInfo("External Reload triggered");
|
||||
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
|
||||
|
||||
@@ -16,10 +16,12 @@ using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.Settings;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
@@ -60,9 +62,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
private readonly ToastWindow _toast = new();
|
||||
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly TopLevelCommandManager tlcManager;
|
||||
|
||||
private readonly CompositeFormat _pageNavigatedAnnouncement;
|
||||
|
||||
private SettingsWindow? _settingsWindow;
|
||||
@@ -70,21 +69,14 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
private CancellationTokenSource? _focusAfterLoadedCts;
|
||||
private WeakReference<Page>? _lastNavigatedPageRef;
|
||||
|
||||
public ShellViewModel ViewModel { get; private set; }
|
||||
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public ShellPage(
|
||||
ShellViewModel shellViewModel,
|
||||
SettingsModel settingsModel,
|
||||
TopLevelCommandManager topLevelCommandManager)
|
||||
public ShellPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
ViewModel = shellViewModel;
|
||||
_settings = settingsModel;
|
||||
tlcManager = topLevelCommandManager;
|
||||
|
||||
// how we are doing navigation around
|
||||
WeakReferenceMessenger.Default.Register<NavigateBackMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<OpenSettingsMessage>(this);
|
||||
@@ -120,16 +112,19 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settings.DisableAnimations ? _noAnimation : _slideRightTransition;
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
return settings.DisableAnimations ? _noAnimation : _slideRightTransition;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(NavigateBackMessage message)
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
|
||||
if (RootFrame.CanGoBack)
|
||||
{
|
||||
if (!message.FromBackspace ||
|
||||
_settings.BackspaceGoesBack)
|
||||
settings.BackspaceGoesBack)
|
||||
{
|
||||
GoBack();
|
||||
}
|
||||
@@ -340,6 +335,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
private void SummonOnUiThread(HotkeySummonMessage message)
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
var commandId = message.CommandId;
|
||||
var isRoot = string.IsNullOrEmpty(commandId);
|
||||
if (isRoot)
|
||||
@@ -350,11 +346,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
// Depending on the settings, either
|
||||
// * Go home, or
|
||||
// * Select the search text (if we should remain open on this page)
|
||||
if (_settings.HotkeyGoesHome)
|
||||
if (settings.HotkeyGoesHome)
|
||||
{
|
||||
GoHome(false);
|
||||
}
|
||||
else if (_settings.HighlightSearchOnActivate)
|
||||
else if (settings.HighlightSearchOnActivate)
|
||||
{
|
||||
SearchBox.SelectSearch();
|
||||
}
|
||||
@@ -364,7 +360,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
try
|
||||
{
|
||||
// For a hotkey bound to a command, first lookup the
|
||||
// command from our list of top level commands.
|
||||
// command from our list of toplevel commands.
|
||||
var tlcManager = App.Current.Services.GetService<TopLevelCommandManager>()!;
|
||||
var topLevelCommand = tlcManager.LookupCommand(commandId);
|
||||
if (topLevelCommand is not null)
|
||||
{
|
||||
@@ -658,15 +655,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
e.Handled = true;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
break;
|
||||
}
|
||||
{
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.MainPage;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WinRT;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
@@ -18,23 +19,24 @@ namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed class PowerToysRootPageService : IRootPageService
|
||||
{
|
||||
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private IExtensionWrapper? _activeExtension;
|
||||
private Lazy<MainListPage> _mainListPage;
|
||||
|
||||
public PowerToysRootPageService(TopLevelCommandManager topLevelCommandManager, SettingsModel settingsModel, AliasManager aliasManager, AppStateModel appStateModel)
|
||||
public PowerToysRootPageService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_topLevelCommandManager = topLevelCommandManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
_mainListPage = new Lazy<MainListPage>(() =>
|
||||
{
|
||||
return new MainListPage(_topLevelCommandManager, settingsModel, aliasManager, appStateModel);
|
||||
return new MainListPage(_serviceProvider);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task PreLoadAsync()
|
||||
{
|
||||
await _topLevelCommandManager.LoadBuiltinsAsync();
|
||||
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
await tlcManager.LoadBuiltinsAsync();
|
||||
}
|
||||
|
||||
public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
|
||||
@@ -44,11 +46,13 @@ internal sealed class PowerToysRootPageService : IRootPageService
|
||||
|
||||
public async Task PostLoadRootPageAsync()
|
||||
{
|
||||
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
|
||||
_topLevelCommandManager.LoadExtensionsCommand.Execute(null);
|
||||
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;
|
||||
|
||||
await _topLevelCommandManager.LoadExtensionsCommand.ExecutionTask!;
|
||||
if (_topLevelCommandManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
// After loading built-ins, and starting navigation, kick off a thread to load extensions.
|
||||
tlcManager.LoadExtensionsCommand.Execute(null);
|
||||
|
||||
await tlcManager.LoadExtensionsCommand.ExecutionTask!;
|
||||
if (tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
// TODO: Handle failure case
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class ExtensionPage : Page
|
||||
{
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
public ProviderSettingsViewModel? ViewModel { get; private set; }
|
||||
|
||||
public ExtensionPage()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
@@ -13,13 +14,16 @@ namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class ExtensionsPage : Page
|
||||
{
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
private readonly SettingsViewModel? viewModel;
|
||||
|
||||
public ExtensionsPage(SettingsViewModel settingsViewModel)
|
||||
public ExtensionsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
viewModel = settingsViewModel;
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
viewModel = new SettingsViewModel(settings, App.Current.Services, _mainTaskScheduler);
|
||||
}
|
||||
|
||||
private void SettingsCard_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
@@ -10,13 +11,16 @@ namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
public sealed partial class GeneralPage : Page
|
||||
{
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
private readonly SettingsViewModel? viewModel;
|
||||
|
||||
public GeneralPage(SettingsViewModel settings, TopLevelCommandManager topLevelCommandManager)
|
||||
public GeneralPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
viewModel = settings;
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
viewModel = new SettingsViewModel(settings, App.Current.Services, _mainTaskScheduler);
|
||||
}
|
||||
|
||||
public string ApplicationVersion
|
||||
|
||||
@@ -429,7 +429,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<value>More</value>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
<data name="TrayMenu_Settings" xml:space="preserve">
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
[JsonSerializable(typeof(List<Choice>))]
|
||||
[JsonSerializable(typeof(List<ChoiceSetSetting>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
|
||||
[JsonSerializable(typeof(List<Dictionary<string, object>>))]
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
|
||||
internal sealed partial class JsonSerializationContext : JsonSerializerContext
|
||||
{
|
||||
|
||||
@@ -26,69 +26,15 @@ public sealed class ToggleSetting : Setting<bool>
|
||||
|
||||
public override Dictionary<string, object> ToDictionary()
|
||||
{
|
||||
var items = new List<Dictionary<string, object>>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Label))
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
items.Add(
|
||||
new()
|
||||
{
|
||||
{ "type", "TextBlock" },
|
||||
{ "text", Label },
|
||||
{ "wrap", true },
|
||||
});
|
||||
}
|
||||
|
||||
if (!(string.IsNullOrEmpty(Description) || string.Equals(Description, Label, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
items.Add(
|
||||
new()
|
||||
{
|
||||
{ "type", "TextBlock" },
|
||||
{ "text", Description },
|
||||
{ "isSubtle", true },
|
||||
{ "size", "Small" },
|
||||
{ "spacing", "Small" },
|
||||
{ "wrap", true },
|
||||
});
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
{ "type", "ColumnSet" },
|
||||
{
|
||||
"columns", new List<Dictionary<string, object>>
|
||||
{
|
||||
new()
|
||||
{
|
||||
{ "type", "Column" },
|
||||
{ "width", "20px" },
|
||||
{
|
||||
"items", new List<Dictionary<string, object>>
|
||||
{
|
||||
new()
|
||||
{
|
||||
{ "type", "Input.Toggle" },
|
||||
{ "title", " " },
|
||||
{ "id", Key },
|
||||
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
|
||||
{ "isRequired", IsRequired },
|
||||
{ "errorMessage", ErrorMessage },
|
||||
},
|
||||
}
|
||||
},
|
||||
{ "verticalContentAlignment", "Center" },
|
||||
},
|
||||
new()
|
||||
{
|
||||
{ "type", "Column" },
|
||||
{ "width", "stretch" },
|
||||
{ "items", items },
|
||||
{ "verticalContentAlignment", "Center" },
|
||||
},
|
||||
}
|
||||
},
|
||||
{ "spacing", "Medium" },
|
||||
{ "type", "Input.Toggle" },
|
||||
{ "title", Label },
|
||||
{ "id", Key },
|
||||
{ "label", Description },
|
||||
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
|
||||
{ "isRequired", IsRequired },
|
||||
{ "errorMessage", ErrorMessage },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 550 B |
Binary file not shown.
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 525 B |
@@ -0,0 +1,35 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for migrating legacy Advanced Paste settings to the updated schema.
|
||||
/// </summary>
|
||||
public static class AdvancedPasteMigrationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance.</param>
|
||||
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
|
||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
const string serviceTypeKey = "OpenAI";
|
||||
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, serviceTypeKey, StringComparison.OrdinalIgnoreCase));
|
||||
bool updated = false;
|
||||
|
||||
if (existingProvider is null)
|
||||
{
|
||||
existingProvider = CreateProvider(serviceTypeKey);
|
||||
configuration.Providers.Add(existingProvider);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
updated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
||||
|
||||
return (existingProvider, updated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a provider with default values for the requested service type.
|
||||
/// </summary>
|
||||
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey)
|
||||
{
|
||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
||||
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||
var provider = new PasteAIProviderDefinition
|
||||
{
|
||||
ServiceType = serviceTypeKey,
|
||||
ModelName = PasteAIProviderDefaults.GetDefaultModelName(serviceType),
|
||||
EndpointUrl = string.Empty,
|
||||
ApiVersion = string.Empty,
|
||||
DeploymentName = string.Empty,
|
||||
ModelPath = string.Empty,
|
||||
SystemPrompt = string.Empty,
|
||||
ModerationEnabled = serviceType == AIServiceType.OpenAI,
|
||||
IsLocalModel = metadata.IsLocalModel,
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
|
||||
{
|
||||
if (configuration?.Providers is null || configuration.Providers.Count == 0)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(configuration?.ActiveProviderId))
|
||||
{
|
||||
configuration.ActiveProviderId = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool updated = false;
|
||||
|
||||
var activeProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase));
|
||||
if (activeProvider is null)
|
||||
{
|
||||
activeProvider = preferredProvider ?? configuration.Providers.First();
|
||||
configuration.ActiveProviderId = activeProvider.Id;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
foreach (var provider in configuration.Providers)
|
||||
{
|
||||
bool shouldBeActive = string.Equals(provider.Id, configuration.ActiveProviderId, StringComparison.OrdinalIgnoreCase);
|
||||
if (provider.IsActive != shouldBeActive)
|
||||
{
|
||||
provider.IsActive = shouldBeActive;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,48 +27,35 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new();
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool IsAIEnabled { get; set; }
|
||||
|
||||
private bool? _legacyAdvancedAIEnabled;
|
||||
|
||||
[JsonPropertyName("IsAdvancedAIEnabled")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public BoolProperty LegacyAdvancedAIEnabledProperty
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, JsonElement> ExtensionData
|
||||
{
|
||||
get => null;
|
||||
get => _extensionData;
|
||||
set
|
||||
{
|
||||
if (value is not null)
|
||||
_extensionData = value;
|
||||
|
||||
if (_extensionData != null && _extensionData.TryGetValue("IsOpenAIEnabled", out var legacyElement) && legacyElement.ValueKind == JsonValueKind.Object && legacyElement.TryGetProperty("value", out var valueElement))
|
||||
{
|
||||
LegacyAdvancedAIEnabled = value.Value;
|
||||
IsAIEnabled = valueElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => IsAIEnabled,
|
||||
};
|
||||
|
||||
_extensionData.Remove("IsOpenAIEnabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool? LegacyAdvancedAIEnabled
|
||||
{
|
||||
get => _legacyAdvancedAIEnabled;
|
||||
private set => _legacyAdvancedAIEnabled = value;
|
||||
}
|
||||
|
||||
public bool TryConsumeLegacyAdvancedAIEnabled(out bool value)
|
||||
{
|
||||
if (_legacyAdvancedAIEnabled is bool flag)
|
||||
{
|
||||
value = flag;
|
||||
_legacyAdvancedAIEnabled = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
private Dictionary<string, JsonElement> _extensionData;
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowCustomPreview { get; set; }
|
||||
@@ -76,9 +63,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool CloseAfterLosingFocus { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool EnableClipboardPreview { get; set; }
|
||||
|
||||
[JsonPropertyName("advanced-paste-ui-hotkey")]
|
||||
public HotkeySettings AdvancedPasteUIShortcut { get; set; }
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
private string _activeProviderId = string.Empty;
|
||||
private ObservableCollection<PasteAIProviderDefinition> _providers = new();
|
||||
private bool _useSharedCredentials = true;
|
||||
private Dictionary<string, AIProviderConfigurationSnapshot> _legacyProviderConfigurations;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
@@ -37,6 +39,21 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
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]
|
||||
public PasteAIProviderDefinition ActiveProvider
|
||||
{
|
||||
|
||||
@@ -1,29 +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.
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides default values for Paste AI provider definitions.
|
||||
/// </summary>
|
||||
public static class PasteAIProviderDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default model name for a given AI service type.
|
||||
/// </summary>
|
||||
public static string GetDefaultModelName(AIServiceType serviceType)
|
||||
{
|
||||
return serviceType switch
|
||||
{
|
||||
AIServiceType.OpenAI => "gpt-4o",
|
||||
AIServiceType.AzureOpenAI => "gpt-4o",
|
||||
AIServiceType.Mistral => "mistral-large-latest",
|
||||
AIServiceType.Google => "gemini-1.5-pro",
|
||||
AIServiceType.AzureAIInference => "gpt-4o-mini",
|
||||
AIServiceType.Ollama => "llama3",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,8 @@
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
x:Name="LoadingStatusTextBlock"
|
||||
x:Uid="AdvancedPaste_FL_LoadingStatus"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Loading Foundry Local status..."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
@@ -57,6 +56,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
x:Name="NoModelsPanel"
|
||||
Grid.Row="0"
|
||||
@@ -64,28 +64,24 @@
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<FontIcon
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="24"
|
||||
Glyph="" />
|
||||
<FontIcon FontSize="24" Glyph="" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_NoModelsDownloaded."
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="No models downloaded"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_RunFoundryLocalText.Text"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Run Foundry Local to download or add a local model below."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="LaunchFoundryModelListButton"
|
||||
x:Uid="AdvancedPaste_FL_OpenFoundryModelList"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Click="LaunchFoundryModelListButton_Click"
|
||||
Content="Open Foundry model list"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -105,10 +101,10 @@
|
||||
SelectionChanged="CachedModelsComboBox_SelectionChanged">
|
||||
<ComboBox.Header>
|
||||
<TextBlock>
|
||||
<Run x:Uid="AdvancedPaste_FL_LocalModel" /><LineBreak /><Run
|
||||
x:Uid="AdvancedPaste_FL_UseCLIToDownloadModels"
|
||||
<Run Text="Foundry Local model" /><LineBreak /><Run
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Use the Foundry Local CLI to download models that run locally on-device. They'll appear here." />
|
||||
</TextBlock>
|
||||
</ComboBox.Header>
|
||||
</ComboBox>
|
||||
@@ -118,11 +114,9 @@
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Bottom"
|
||||
Click="RefreshModelsButton_Click"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Refresh model list">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="AdvancedPaste_FL_RefreshModelList" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel
|
||||
@@ -152,28 +146,24 @@
|
||||
Spacing="8">
|
||||
<Image Width="36" Source="ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_FLNotavailableYet"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Foundry Local is not available on this device yet."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
x:Uid="AdvancedPaste_FL_StartService"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AdvancedPaste_FL_CLIGuide"
|
||||
HorizontalAlignment="Center"
|
||||
NavigateUri="https://learn.microsoft.com/azure/ai-foundry/foundry-local/get-started" />
|
||||
TextWrapping="Wrap">
|
||||
<Run Text="Start the Foundry Local service before returning to PowerToys." />
|
||||
</TextBlock>
|
||||
<HyperlinkButton Content="Follow the Foundry Local CLI guide" NavigateUri="https://learn.microsoft.com/en-us/azure/ai-foundry/foundry-local/get-started" />
|
||||
<TextBlock
|
||||
x:Uid="FoundryLocal_RestartRequiredNote"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Note: After installing the Foundry Local CLI, restart PowerToys to use it."
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -103,7 +103,10 @@
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander.Description>
|
||||
<tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_ModelProviders" Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<tkcontrols:SettingsCard
|
||||
Description="Add online or local models"
|
||||
Header="Model providers"
|
||||
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<Button Content="Add model" Style="{StaticResource AccentButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout x:Name="AddProviderMenuFlyout" Opening="AddProviderMenuFlyout_Opening" />
|
||||
@@ -127,16 +130,16 @@
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="AdvancedPaste_Edit"
|
||||
Click="EditPasteAIProviderButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}" />
|
||||
Tag="{x:Bind}"
|
||||
Text="Edit" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="AdvancedPaste_Remove"
|
||||
Click="RemovePasteAIProviderButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="{x:Bind}" />
|
||||
Tag="{x:Bind}"
|
||||
Text="Remove" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
@@ -168,9 +171,6 @@
|
||||
</InfoBar>
|
||||
</tkcontrols:SettingsExpander.ItemsHeader>
|
||||
<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
|
||||
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
|
||||
ContentAlignment="Left"
|
||||
@@ -429,11 +429,12 @@
|
||||
<!-- Paste AI provider dialog -->
|
||||
<ContentDialog
|
||||
x:Name="PasteAIProviderConfigurationDialog"
|
||||
x:Uid="AdvancedPaste_EndpointDialog"
|
||||
Title="Paste with AI provider configuration"
|
||||
Closed="PasteAIProviderConfigurationDialog_Closed"
|
||||
PrimaryButtonClick="PasteAIProviderConfigurationDialog_PrimaryButtonClick"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
||||
@@ -495,44 +496,46 @@
|
||||
Spacing="16">
|
||||
<TextBox
|
||||
x:Name="PasteAIModelNameTextBox"
|
||||
x:Uid="AdvancedPaste_ModelName"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
PlaceholderText="gpt-4o"
|
||||
Header="Model name"
|
||||
PlaceholderText="gpt-4"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.ModelName, Mode=TwoWay}" />
|
||||
<TextBox
|
||||
x:Name="PasteAIEndpointUrlTextBox"
|
||||
x:Uid="AdvancedPaste_EndpointURL"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
Header="Endpoint URL"
|
||||
PlaceholderText="https://your-resource.openai.azure.com/"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.EndpointUrl, Mode=TwoWay}" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="PasteAIApiKeyPasswordBox"
|
||||
x:Uid="AdvancedPaste_APIKey"
|
||||
MinWidth="200" />
|
||||
MinWidth="200"
|
||||
Header="API key"
|
||||
PlaceholderText="Enter API Key" />
|
||||
<TextBox
|
||||
x:Name="PasteAIApiVersionTextBox"
|
||||
x:Uid="AdvancedPaste_APIVersion"
|
||||
MinWidth="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
Header="API version"
|
||||
PlaceholderText="2024-10-01"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.ApiVersion, Mode=TwoWay}"
|
||||
Visibility="Collapsed" />
|
||||
<TextBox
|
||||
x:Name="PasteAIDeploymentNameTextBox"
|
||||
x:Uid="AdvancedPaste_DeploymentName"
|
||||
MinWidth="200"
|
||||
PlaceholderText="gpt-4o"
|
||||
Header="Deployment name"
|
||||
PlaceholderText="gpt-4"
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.DeploymentName, Mode=TwoWay}" />
|
||||
<TextBox
|
||||
x:Name="PasteAISystemPromptTextBox"
|
||||
x:Uid="AdvancedPaste_SystemPrompt"
|
||||
MinWidth="200"
|
||||
MinHeight="76"
|
||||
HorizontalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
Header="System prompt"
|
||||
PlaceholderText="You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. Do not output anything else besides the reformatted clipboard content."
|
||||
Text="{x:Bind ViewModel.PasteAIProviderDraft.SystemPrompt, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<Grid
|
||||
@@ -572,10 +575,13 @@
|
||||
IsOn="{x:Bind ViewModel.PasteAIProviderDraft.EnableAdvancedAI, Mode=TwoWay}"
|
||||
Toggled="PasteAIEnableAdvancedAICheckBox_Toggled"
|
||||
Visibility="Collapsed">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="" TextWrapping="Wrap" />
|
||||
</ToolTipService.ToolTip>
|
||||
<ToggleSwitch.Header>
|
||||
<TextBlock>
|
||||
<Run x:Uid="AdvancedPaste_EnableAdvancedAI" /> <LineBreak />
|
||||
<Run x:Uid="AdvancedPaste_EnableAdvancedAIDescription" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<Run Text="Enable Advanced AI" /> <LineBreak />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Use built-in functions to handle complex tasks. Token consumption may increase." />
|
||||
</TextBlock>
|
||||
</ToggleSwitch.Header>
|
||||
</ToggleSwitch>
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable
|
||||
{
|
||||
private readonly ObservableCollection<ModelDetails> _foundryCachedModels = new();
|
||||
private readonly ObservableCollection<FoundryDownloadableModel> _foundryDownloadableModels = new();
|
||||
private CancellationTokenSource _foundryModelLoadCts;
|
||||
private bool _suppressFoundrySelectionChanged;
|
||||
private bool _isFoundryLocalAvailable;
|
||||
@@ -35,8 +36,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private const string AdvancedAISystemPrompt = "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 const string SimpleAISystemPrompt = "You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. Do not output anything else besides the reformatted clipboard content.";
|
||||
private static readonly string AdvancedAISystemPromptNormalized = AdvancedAISystemPrompt.Trim();
|
||||
private static readonly string SimpleAISystemPromptNormalized = SimpleAISystemPrompt.Trim();
|
||||
|
||||
private AdvancedPasteViewModel ViewModel { get; set; }
|
||||
|
||||
@@ -56,6 +55,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
if (FoundryLocalPicker is not null)
|
||||
{
|
||||
FoundryLocalPicker.CachedModels = _foundryCachedModels;
|
||||
FoundryLocalPicker.DownloadableModels = _foundryDownloadableModels;
|
||||
FoundryLocalPicker.SelectionChanged += FoundryLocalPicker_SelectionChanged;
|
||||
FoundryLocalPicker.LoadRequested += FoundryLocalPicker_LoadRequested;
|
||||
}
|
||||
@@ -467,7 +467,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
var cachedModels = cachedModelsEnumerable?.ToList() ?? new List<ModelDetails>();
|
||||
|
||||
UpdateFoundryCollections(cachedModels);
|
||||
UpdateFoundryCollections(cachedModels, []);
|
||||
ShowFoundryAvailableState();
|
||||
RestoreFoundrySelection(cachedModels);
|
||||
}
|
||||
@@ -536,7 +536,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
UpdateFoundrySaveButtonState();
|
||||
}
|
||||
|
||||
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels)
|
||||
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels, IReadOnlyCollection<ModelDetails> catalogModels)
|
||||
{
|
||||
_foundryCachedModels.Clear();
|
||||
|
||||
@@ -545,7 +545,20 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
_foundryCachedModels.Add(model);
|
||||
}
|
||||
|
||||
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => m.Name), StringComparer.OrdinalIgnoreCase);
|
||||
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => NormalizeFoundryModelReference(m.Url ?? 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)
|
||||
@@ -561,8 +574,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(currentModelReference))
|
||||
{
|
||||
var normalizedReference = NormalizeFoundryModelReference(currentModelReference);
|
||||
matchingModel = cachedModels.FirstOrDefault(model =>
|
||||
string.Equals(model.Name, currentModelReference, StringComparison.OrdinalIgnoreCase));
|
||||
string.Equals(NormalizeFoundryModelReference(model.Url ?? model.Name), normalizedReference, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is null)
|
||||
@@ -592,7 +606,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||
{
|
||||
ViewModel.PasteAIProviderDraft.ModelName = matchingModel.Name;
|
||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(matchingModel.Url ?? matchingModel.Name);
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is not null)
|
||||
@@ -604,6 +618,19 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
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()
|
||||
{
|
||||
if (PasteAIProviderConfigurationDialog is null)
|
||||
@@ -627,7 +654,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isFoundryLocalAvailable)
|
||||
if (!_isFoundryLocalAvailable || _foundryDownloadableModels.Any(model => model.IsDownloading))
|
||||
{
|
||||
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
||||
return;
|
||||
@@ -648,7 +675,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||
{
|
||||
ViewModel.PasteAIProviderDraft.ModelName = selectedModel.Name;
|
||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(selectedModel.Url ?? selectedModel.Name);
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is not null)
|
||||
@@ -777,7 +804,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizeSystemPrompt(draft);
|
||||
string serviceType = draft.ServiceType ?? "OpenAI";
|
||||
string apiKey = PasteAIApiKeyPasswordBox.Password;
|
||||
string trimmedApiKey = apiKey?.Trim() ?? string.Empty;
|
||||
@@ -808,8 +834,22 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizeSystemPrompt(draft);
|
||||
UpdateSystemPromptPlaceholder();
|
||||
bool isEmptyOrDefault = string.IsNullOrWhiteSpace(draft.SystemPrompt) ||
|
||||
draft.SystemPrompt.Trim() == AdvancedAISystemPrompt.Trim() ||
|
||||
draft.SystemPrompt.Trim() == SimpleAISystemPrompt.Trim();
|
||||
|
||||
if (isEmptyOrDefault)
|
||||
{
|
||||
if (!draft.EnableAdvancedAI)
|
||||
{
|
||||
// Now we'll switch
|
||||
draft.SystemPrompt = AdvancedAISystemPrompt;
|
||||
}
|
||||
else
|
||||
{
|
||||
draft.SystemPrompt = SimpleAISystemPrompt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RequiresApiKeyForService(string serviceType)
|
||||
@@ -910,47 +950,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private Visibility GetServicePrivacyVisibility(string serviceType) => HasServicePrivacyLink(serviceType) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
private static bool IsPlaceholderSystemPrompt(string prompt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string trimmedPrompt = prompt.Trim();
|
||||
return string.Equals(trimmedPrompt, AdvancedAISystemPromptNormalized, StringComparison.Ordinal)
|
||||
|| string.Equals(trimmedPrompt, SimpleAISystemPromptNormalized, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void NormalizeSystemPrompt(PasteAIProviderDefinition draft)
|
||||
{
|
||||
if (draft is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPlaceholderSystemPrompt(draft.SystemPrompt))
|
||||
{
|
||||
draft.SystemPrompt = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSystemPromptPlaceholder()
|
||||
{
|
||||
var draft = ViewModel?.PasteAIProviderDraft;
|
||||
if (draft is null)
|
||||
if (draft is null || PasteAISystemPromptTextBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizeSystemPrompt(draft);
|
||||
if (PasteAISystemPromptTextBox is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool useAdvancedPlaceholder = PasteAIEnableAdvancedAICheckBox?.IsOn ?? draft.EnableAdvancedAI;
|
||||
PasteAISystemPromptTextBox.PlaceholderText = useAdvancedPlaceholder
|
||||
PasteAISystemPromptTextBox.PlaceholderText = draft.EnableAdvancedAI
|
||||
? AdvancedAISystemPrompt
|
||||
: SimpleAISystemPrompt;
|
||||
}
|
||||
|
||||
@@ -199,8 +199,10 @@
|
||||
<ContentDialog
|
||||
x:Name="LocationDialog"
|
||||
x:Uid="LightSwitch_LocationDialog"
|
||||
Closed="LocationDialog_Closed"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
Opened="LocationDialog_Opened"
|
||||
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<Grid RowSpacing="16">
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private readonly IFileSystemWatcher fileSystemWatcher;
|
||||
private readonly DispatcherQueue dispatcherQueue;
|
||||
private bool suppressViewModelUpdates;
|
||||
private bool suppressLatLonChange = true;
|
||||
private bool latBoxLoaded;
|
||||
private bool lonBoxLoaded;
|
||||
|
||||
private LightSwitchViewModel ViewModel { get; set; }
|
||||
|
||||
@@ -129,6 +132,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
// Since we use this mode, we can remove the selected city data.
|
||||
this.ViewModel.SelectedCity = null;
|
||||
|
||||
this.suppressLatLonChange = false;
|
||||
|
||||
// ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}";
|
||||
this.SyncButton.IsEnabled = true;
|
||||
this.SyncLoader.IsActive = false;
|
||||
@@ -152,10 +157,23 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
|
||||
{
|
||||
if (this.suppressLatLonChange)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double latitude = this.LatitudeBox.Value;
|
||||
double longitude = this.LongitudeBox.Value;
|
||||
|
||||
if (double.IsNaN(latitude) || double.IsNaN(longitude) || (latitude == 0 && longitude == 0))
|
||||
if (double.IsNaN(latitude) || double.IsNaN(longitude))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double viewModelLatitude = double.TryParse(this.ViewModel.Latitude, out var lat) ? lat : 0.0;
|
||||
double viewModelLongitude = double.TryParse(this.ViewModel.Longitude, out var lon) ? lon : 0.0;
|
||||
|
||||
if (Math.Abs(latitude - viewModelLatitude) < 0.0001 && Math.Abs(longitude - viewModelLongitude) < 0.0001)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -165,6 +183,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
this.ViewModel.LocationPanelLightTimeMinutes = (result.SunriseHour * 60) + result.SunriseMinute;
|
||||
this.ViewModel.LocationPanelDarkTimeMinutes = (result.SunsetHour * 60) + result.SunsetMinute;
|
||||
|
||||
// Show the panel with these values
|
||||
this.LocationResultPanel.Visibility = Visibility.Visible;
|
||||
if (this.LocationDialog != null)
|
||||
{
|
||||
@@ -195,6 +214,37 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
this.SunriseModeChartState();
|
||||
}
|
||||
|
||||
private void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
|
||||
{
|
||||
this.LatitudeBox.Loaded += LatLonBox_Loaded;
|
||||
this.LongitudeBox.Loaded += LatLonBox_Loaded;
|
||||
}
|
||||
|
||||
private void LocationDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args)
|
||||
{
|
||||
this.LatitudeBox.Loaded -= LatLonBox_Loaded;
|
||||
this.LongitudeBox.Loaded -= LatLonBox_Loaded;
|
||||
this.latBoxLoaded = false;
|
||||
this.lonBoxLoaded = false;
|
||||
}
|
||||
|
||||
private void LatLonBox_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is NumberBox numberBox && numberBox == this.LatitudeBox && this.LatitudeBox.IsLoaded)
|
||||
{
|
||||
this.latBoxLoaded = true;
|
||||
}
|
||||
else if (sender is NumberBox numberBox2 && numberBox2 == this.LongitudeBox && this.LongitudeBox.IsLoaded)
|
||||
{
|
||||
this.lonBoxLoaded = true;
|
||||
}
|
||||
|
||||
if (this.latBoxLoaded && this.lonBoxLoaded)
|
||||
{
|
||||
this.suppressLatLonChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (this.suppressViewModelUpdates)
|
||||
|
||||
@@ -283,7 +283,6 @@
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsCursorWrapSettingsExpander"
|
||||
x:Uid="MouseUtils_CursorWrap_ActivationShortcut"
|
||||
@@ -295,7 +294,18 @@
|
||||
<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}">
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="CursorWrapAppearanceBehavior"
|
||||
x:Uid="Appearance_Behavior"
|
||||
AutomationProperties.AutomationId="MouseUtils_CursorWrapAppearanceBehaviorId"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}"
|
||||
IsExpanded="False">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
|
||||
@@ -285,9 +285,6 @@
|
||||
AutomationProperties.AutomationId="InputOutputNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/InputOutput.png}"
|
||||
SelectsOnInvoked="False">
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
<NavigationViewItem.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Name="KeyboardManagerNavigationItem"
|
||||
@@ -302,11 +299,7 @@
|
||||
x:Uid="Shell_MouseUtilities"
|
||||
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
|
||||
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}">
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
</NavigationViewItem>
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="MouseWithoutBordersNavigationItem"
|
||||
x:Uid="Shell_MouseWithoutBorders"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
@@ -26,36 +26,36 @@
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -644,14 +644,17 @@ Please review the placeholder content that represents the final terms and usage
|
||||
<data name="AdvancedPaste_EnablePasteAIModerationToggle.Header" xml:space="preserve">
|
||||
<value>Enable OpenAI content moderation</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAIDescription.Text" xml:space="preserve">
|
||||
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Enable Advanced AI</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Use built-in functions to handle complex tasks. Token consumption may increase.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Access Clipboard History</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve">
|
||||
<value>View and select previously copied items</value>
|
||||
<value>Clipboard History shows a list of previously copied items.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Actions</value>
|
||||
@@ -2685,7 +2688,10 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<value>Wrap the mouse cursor between monitor edges</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
|
||||
<value>Activation and behavior</value>
|
||||
<value>Activation shortcut</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Description.Text" xml:space="preserve">
|
||||
<value>Hotkey to toggle cursor wrapping on/off</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
|
||||
<value>Set shortcut</value>
|
||||
@@ -4052,8 +4058,11 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="AdvancedPaste_ShowCustomPreviewSettingsCard.Description" xml:space="preserve">
|
||||
<value>Preview the output of AI formats and Image to text before pasting</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Text" xml:space="preserve">
|
||||
<value>Enable Advanced AI</value>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Header" xml:space="preserve">
|
||||
<value>Advanced AI</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Description" xml:space="preserve">
|
||||
<value>Supports advanced workflows by chaining transformations and working with files and images. May use additional API credits.</value>
|
||||
</data>
|
||||
<data name="Oobe_AdvancedPaste.Description" xml:space="preserve">
|
||||
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value>
|
||||
@@ -4595,13 +4604,9 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<value>If you do not have credits you will see an 'API key quota exceeded' error</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_CloseAfterLosingFocus.Content" xml:space="preserve">
|
||||
<value>Automatically close the window after it loses focus</value>
|
||||
<value>Automatically close the Advanced Paste window after it loses focus</value>
|
||||
<comment>Advanced Paste is a product name, do not loc</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableClipboardPreview.Header" xml:space="preserve">
|
||||
<value>Show clipboard preview</value>
|
||||
<comment>Enables display of clipboard contents preview in the Advanced Paste window</comment>
|
||||
</data>
|
||||
<data name="GPO_CommandNotFound_ForceDisabled.Title" xml:space="preserve">
|
||||
<value>The Command Not Found module is disabled by your organization.</value>
|
||||
<comment>"Command Not Found" is a product name</comment>
|
||||
@@ -5674,83 +5679,4 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="LightSwitch_SetLocationButton.Content" xml:space="preserve">
|
||||
<value>Set Location</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_OpenFoundryModelList.Content" xml:space="preserve">
|
||||
<value>Open Foundry Local model list</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_RunFoundryLocalText.Text" xml:space="preserve">
|
||||
<value>Run Foundry Local to download or add a local model</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_NoModelsDownloaded.Text" xml:space="preserve">
|
||||
<value>No models downloaded</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_LoadingStatus.Text" xml:space="preserve">
|
||||
<value>Loading Foundry Local status..</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_LocalModel.Text" xml:space="preserve">
|
||||
<value>Foundry Local model</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_UseCLIToDownloadModels.Text" xml:space="preserve">
|
||||
<value>Use the Foundry Local CLI to download models that run locally on-device. They'll appear here.</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_RefreshModelList.Text" xml:space="preserve">
|
||||
<value>Refresh model list</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_FLNotavailableYet.Text" xml:space="preserve">
|
||||
<value>Foundry Local is not available on this device yet.</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_StartService.Text" xml:space="preserve">
|
||||
<value>Start the Foundry Local service before returning to PowerToys.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_FL_CLIGuide.Content" xml:space="preserve">
|
||||
<value>Follow the Foundry Local CLI guide</value>
|
||||
<comment>Do not localize "Foundry Local", it's a product name</comment>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelProviders.Header" xml:space="preserve">
|
||||
<value>Model providers</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelProviders.Description" xml:space="preserve">
|
||||
<value>Add online or local models</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Edit.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Remove.Text" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ModelName.Header" xml:space="preserve">
|
||||
<value>Model name</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EndpointURL.Header" xml:space="preserve">
|
||||
<value>Endpoint URL</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIKey.Header" xml:space="preserve">
|
||||
<value>API key</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIKey.PlaceholderText" xml:space="preserve">
|
||||
<value>Enter API key</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_APIVersion.Header" xml:space="preserve">
|
||||
<value>API version</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_DeploymentName.Header" xml:space="preserve">
|
||||
<value>Deployment name</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_SystemPrompt.Header" xml:space="preserve">
|
||||
<value>System prompt</value>
|
||||
</data>
|
||||
<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>
|
||||
</root>
|
||||
@@ -172,86 +172,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private void MigrateLegacyAIEnablement()
|
||||
{
|
||||
var properties = _advancedPasteSettings?.Properties;
|
||||
if (properties is null)
|
||||
if (_advancedPasteSettings.Properties.IsAIEnabled || IsOnlineAIModelsDisallowedByGPO)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
|
||||
if (IsOnlineAIModelsDisallowedByGPO)
|
||||
if (!LegacyOpenAIKeyExists())
|
||||
{
|
||||
if (legacyAdvancedAIConsumed)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (legacyCredential is null)
|
||||
{
|
||||
if (legacyAdvancedAIConsumed)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = new PasteAIConfiguration();
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
|
||||
configurationUpdated = true;
|
||||
}
|
||||
|
||||
if (legacyCredential is not null && openAIProvider is not null)
|
||||
{
|
||||
SavePasteAIApiKey(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
const bool shouldEnableAI = true;
|
||||
bool enabledChanged = false;
|
||||
if (properties.IsAIEnabled != shouldEnableAI)
|
||||
{
|
||||
properties.IsAIEnabled = shouldEnableAI;
|
||||
enabledChanged = true;
|
||||
}
|
||||
|
||||
bool shouldPersist = configurationUpdated || enabledChanged || legacyAdvancedAIConsumed;
|
||||
|
||||
if (shouldPersist)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
|
||||
if (configurationUpdated)
|
||||
{
|
||||
OnPropertyChanged(nameof(PasteAIConfiguration));
|
||||
}
|
||||
|
||||
if (enabledChanged)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsAIEnabled));
|
||||
}
|
||||
}
|
||||
_advancedPasteSettings.Properties.IsAIEnabled = true;
|
||||
SaveAndNotifySettings();
|
||||
OnPropertyChanged(nameof(IsAIEnabled));
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
@@ -296,30 +229,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public bool IsAIEnabled => _advancedPasteSettings.Properties.IsAIEnabled && !IsOnlineAIModelsDisallowedByGPO;
|
||||
|
||||
private PasswordCredential TryGetLegacyOpenAICredential()
|
||||
private bool LegacyOpenAIKeyExists()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
credential?.RetrievePassword();
|
||||
return credential;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveLegacyOpenAICredential()
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new();
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
// return vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey") is not null;
|
||||
var legacyOpenAIKey = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
if (legacyOpenAIKey != null)
|
||||
{
|
||||
string credentialResource = GetAICredentialResource("OpenAI");
|
||||
var targetProvider = PasteAIConfiguration?.ActiveProvider ?? PasteAIConfiguration?.Providers?.FirstOrDefault();
|
||||
string providerId = targetProvider?.Id ?? string.Empty;
|
||||
string serviceType = targetProvider?.ServiceType ?? "OpenAI";
|
||||
string credentialUserName = GetPasteAICredentialUserName(providerId, serviceType);
|
||||
PasswordCredential cred = new(credentialResource, credentialUserName, legacyOpenAIKey.Password);
|
||||
vault.Add(cred);
|
||||
|
||||
// delete old key
|
||||
TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,19 +479,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableClipboardPreview
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.EnableClipboardPreview;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.EnableClipboardPreview)
|
||||
{
|
||||
_advancedPasteSettings.Properties.EnableClipboardPreview = value;
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConflictingCopyShortcut =>
|
||||
_customActions.Select(customAction => customAction.Shortcut)
|
||||
.Concat([PasteAsPlainTextShortcut, AdvancedPasteUIShortcut, PasteAsMarkdownShortcut, PasteAsJsonShortcut])
|
||||
@@ -595,7 +519,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
var provider = new PasteAIProviderDefinition
|
||||
{
|
||||
ServiceType = persistedServiceType,
|
||||
ModelName = PasteAIProviderDefaults.GetDefaultModelName(normalizedServiceType),
|
||||
ModelName = GetDefaultModelName(normalizedServiceType),
|
||||
EndpointUrl = string.Empty,
|
||||
ApiVersion = string.Empty,
|
||||
DeploymentName = string.Empty,
|
||||
@@ -635,6 +559,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return serviceTypeKind;
|
||||
}
|
||||
|
||||
private static string GetDefaultModelName(AIServiceType serviceType)
|
||||
{
|
||||
return serviceType switch
|
||||
{
|
||||
AIServiceType.OpenAI => "gpt-4",
|
||||
AIServiceType.AzureOpenAI => "gpt-4",
|
||||
AIServiceType.Mistral => "mistral-large-latest",
|
||||
AIServiceType.Google => "gemini-2.5-pro",
|
||||
AIServiceType.AzureAIInference => "gpt-4o-mini",
|
||||
AIServiceType.Ollama => "llama3",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public bool IsServiceTypeAllowedByGPO(AIServiceType serviceType)
|
||||
{
|
||||
var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||
@@ -1213,12 +1151,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
OnPropertyChanged(nameof(CloseAfterLosingFocus));
|
||||
}
|
||||
|
||||
if (target.EnableClipboardPreview != source.EnableClipboardPreview)
|
||||
{
|
||||
target.EnableClipboardPreview = source.EnableClipboardPreview;
|
||||
OnPropertyChanged(nameof(EnableClipboardPreview));
|
||||
}
|
||||
|
||||
var incomingConfig = source.PasteAIConfiguration ?? new PasteAIConfiguration();
|
||||
if (ShouldReplacePasteAIConfiguration(target.PasteAIConfiguration, incomingConfig))
|
||||
{
|
||||
@@ -1243,6 +1175,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return true;
|
||||
}
|
||||
|
||||
if (current.UseSharedCredentials != incoming.UseSharedCredentials)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var currentProviders = current.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
||||
var incomingProviders = incoming.Providers ?? new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
@@ -1398,7 +1335,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
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();
|
||||
}
|
||||
@@ -1414,7 +1352,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
SubscribeToPasteAIProviders(pasteConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)),
|
||||
IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled,
|
||||
Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||
IsNew = moduleType == ModuleType.CursorWrap,
|
||||
DashboardModuleItems = GetModuleItems(moduleType),
|
||||
};
|
||||
newItem.EnabledChangedCallback = EnabledChangedOnUI;
|
||||
|
||||
@@ -562,7 +562,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
SyncButtonInformation = SelectedCity != null
|
||||
? SelectedCity.City
|
||||
: $"{Latitude}°,{Longitude}°";
|
||||
: $"{Latitude},{Longitude}";
|
||||
|
||||
double lat = double.Parse(ModuleSettings.Properties.Latitude.Value, CultureInfo.InvariantCulture);
|
||||
double lon = double.Parse(ModuleSettings.Properties.Longitude.Value, CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -1000,13 +1000,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
GeneralSettingsConfig.Enabled.CursorWrap = value;
|
||||
OnPropertyChanged(nameof(IsCursorWrapEnabled));
|
||||
|
||||
// Auto-enable the AutoActivate setting when CursorWrap is enabled
|
||||
// This ensures cursor wrapping is active immediately after enabling
|
||||
if (value && !_cursorWrapAutoActivate)
|
||||
{
|
||||
CursorWrapAutoActivate = true;
|
||||
}
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public class ZoomItViewModel : Observable
|
||||
{
|
||||
private const string FormatGif = "GIF";
|
||||
private const string FormatMp4 = "MP4";
|
||||
|
||||
private ISettingsUtils SettingsUtils { get; set; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
@@ -659,12 +656,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
@@ -675,19 +672,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
set
|
||||
{
|
||||
int format = 0;
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatGif)
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||
{
|
||||
format = 0;
|
||||
}
|
||||
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == FormatMp4)
|
||||
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||
{
|
||||
format = 1;
|
||||
}
|
||||
|
||||
if (format != value)
|
||||
{
|
||||
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? FormatGif : FormatMp4;
|
||||
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4";
|
||||
OnPropertyChanged(nameof(RecordFormatIndex));
|
||||
NotifySettingsChanged();
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>../../../src/</AdditionalIncludeDirectories>
|
||||
<TreatAngleIncludeAsExternal>true</TreatAngleIncludeAsExternal>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
@@ -38,6 +37,10 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\..\deps\cziplib\src\zip.c">
|
||||
<!-- Disabling warnings for external code -->
|
||||
<DisableSpecificWarnings>4706;26451;4267;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EventViewer.cpp" />
|
||||
<ClCompile Include="InstallationFolder.cpp" />
|
||||
<ClCompile Include="Package.cpp" />
|
||||
@@ -58,6 +61,8 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\..\deps\cziplib\src\miniz.h" />
|
||||
<ClInclude Include="..\..\..\deps\cziplib\src\zip.h" />
|
||||
<ClInclude Include="EventViewer.h" />
|
||||
<ClInclude Include="InstallationFolder.h" />
|
||||
<ClInclude Include="Package.h" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<ClCompile Include="ZipTools\ZipFolder.cpp">
|
||||
<Filter>ZipTools</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\deps\cziplib\src\zip.c" />
|
||||
<ClCompile Include="ReportMonitorInfo.cpp" />
|
||||
<ClCompile Include="RegistryUtils.cpp" />
|
||||
<ClCompile Include="EventViewer.cpp" />
|
||||
@@ -27,6 +28,8 @@
|
||||
<Filter>ZipTools</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\..\common\utils\json.h" />
|
||||
<ClInclude Include="..\..\..\deps\cziplib\src\miniz.h" />
|
||||
<ClInclude Include="..\..\..\deps\cziplib\src\zip.h" />
|
||||
<ClInclude Include="ReportMonitorInfo.h" />
|
||||
<ClInclude Include="RegistryUtils.h" />
|
||||
<ClInclude Include="EventViewer.h" />
|
||||
|
||||
@@ -1,53 +1,50 @@
|
||||
#include "ZipFolder.h"
|
||||
#include "..\..\..\..\deps\cziplib\src\zip.h"
|
||||
#include <common/utils/timeutil.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#include <format>
|
||||
#include <wil/stl.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
|
||||
void ZipFolder(std::filesystem::path zipPath, std::filesystem::path folderPath)
|
||||
{
|
||||
const auto reportFilename{
|
||||
std::format("PowerToysReport_{0}.zip",
|
||||
timeutil::format_as_local("%F-%H-%M-%S", timeutil::now()))
|
||||
};
|
||||
const auto finalReportFullPath{ zipPath / reportFilename };
|
||||
std::string reportFilename{ "PowerToysReport_" };
|
||||
reportFilename += timeutil::format_as_local("%F-%H-%M-%S", timeutil::now());
|
||||
reportFilename += ".zip";
|
||||
|
||||
const auto tempReportFilename{ reportFilename + ".tmp" };
|
||||
const auto tempReportFullPath{ zipPath / tempReportFilename };
|
||||
auto tmpZipPath = std::filesystem::temp_directory_path();
|
||||
tmpZipPath /= reportFilename;
|
||||
|
||||
// tar -c --format=zip -f "ReportFile.zip" *
|
||||
const auto executable{ wil::ExpandEnvironmentStringsW<std::wstring>(LR"(%WINDIR%\System32\tar.exe)") };
|
||||
auto commandline{ std::format(LR"("{0}" -c --format=zip -f "{1}" *)", executable, tempReportFullPath.wstring()) };
|
||||
|
||||
const auto folderPathAsString{ folderPath.lexically_normal().wstring() };
|
||||
|
||||
wil::unique_process_information pi;
|
||||
STARTUPINFOW si{ .cb = sizeof(STARTUPINFOW) };
|
||||
if (!CreateProcessW(executable.c_str(),
|
||||
commandline.data() /* must be mutable */,
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
DETACHED_PROCESS,
|
||||
nullptr,
|
||||
folderPathAsString.c_str(),
|
||||
&si,
|
||||
&pi))
|
||||
struct zip_t* zip = zip_open(tmpZipPath.string().c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
||||
if (!zip)
|
||||
{
|
||||
printf("Cannot open zip.");
|
||||
throw -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
|
||||
const size_t rootSize = folderPath.wstring().size();
|
||||
for (const auto& dirEntry : recursive_directory_iterator(folderPath))
|
||||
{
|
||||
if (dirEntry.is_regular_file())
|
||||
{
|
||||
auto path = dirEntry.path().string();
|
||||
auto relativePath = path.substr(rootSize, path.size());
|
||||
zip_entry_open(zip, relativePath.c_str());
|
||||
zip_entry_fwrite(zip, path.c_str());
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code err{};
|
||||
std::filesystem::rename(tempReportFullPath, finalReportFullPath, err);
|
||||
zip_close(zip);
|
||||
|
||||
std::error_code err;
|
||||
std::filesystem::copy(tmpZipPath, zipPath, err);
|
||||
if (err.value() != 0)
|
||||
{
|
||||
wprintf_s(L"Failed to rename %s. Error code: %d\n", tempReportFullPath.native().c_str(), err.value());
|
||||
wprintf_s(L"Failed to copy %s. Error code: %d\n", tmpZipPath.c_str(), err.value());
|
||||
}
|
||||
}
|
||||
|
||||
err = {};
|
||||
std::filesystem::remove_all(tmpZipPath, err);
|
||||
if (err.value() != 0)
|
||||
{
|
||||
wprintf_s(L"Failed to delete %s. Error code: %d\n", tmpZipPath.c_str(), err.value());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user