mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-05 20:06:59 +01:00
Compare commits
21 Commits
shawn/fixA
...
shawn/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
013a1763b5 | ||
|
|
4668b16700 | ||
|
|
ed12ba8f4d | ||
|
|
d517730fd0 | ||
|
|
8dfa3ae8fa | ||
|
|
193d9aacbe | ||
|
|
483e773299 | ||
|
|
dd4c7ba57e | ||
|
|
29534601be | ||
|
|
74225b01ce | ||
|
|
2f001e8150 | ||
|
|
a983a773f3 | ||
|
|
02eb55e3b9 | ||
|
|
afaf904016 | ||
|
|
4425e3ad28 | ||
|
|
9f95d9b477 | ||
|
|
8fc43e1a22 | ||
|
|
42ebf8d992 | ||
|
|
76b6a25ac4 | ||
|
|
1c646ecb2a | ||
|
|
a51b2647d9 |
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,6 +1873,7 @@ UPDATENOW
|
||||
UPDATEREGISTRY
|
||||
updown
|
||||
UPGRADINGPRODUCTCODE
|
||||
upscaling
|
||||
Uptool
|
||||
urld
|
||||
Usb
|
||||
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -5,6 +5,7 @@
|
||||
## 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@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,6 +4,3 @@
|
||||
[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,6 +26,7 @@
|
||||
<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 -->
|
||||
@@ -34,7 +35,7 @@
|
||||
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
|
||||
<VcpkgEnabled>false</VcpkgEnabled>
|
||||
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
|
||||
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath)</ExternalIncludePath>
|
||||
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)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,6 +147,18 @@ _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
|
||||
|
||||
1
deps/cziplib
vendored
1
deps/cziplib
vendored
Submodule deps/cziplib deleted from 81314fff0a
@@ -205,4 +205,12 @@ 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? _foundryManager;
|
||||
private FoundryClient? _foundryClient;
|
||||
private string? _serviceUrl;
|
||||
|
||||
public static FoundryLocalModelProvider Instance { get; } = new();
|
||||
@@ -22,13 +22,11 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
|
||||
public string ProviderDescription => "The model will run locally via Foundry Local";
|
||||
|
||||
public string UrlPrefix => "fl://";
|
||||
|
||||
public IChatClient? GetIChatClient(string url)
|
||||
public IChatClient? GetIChatClient(string modelId)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {url}");
|
||||
Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}");
|
||||
InitializeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -37,26 +35,22 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryManager == null)
|
||||
if (string.IsNullOrWhiteSpace(_serviceUrl) || _foundryClient == 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 = _foundryManager.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
var isLoaded = _foundryClient.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
|
||||
if (!isLoaded)
|
||||
{
|
||||
Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
|
||||
@@ -72,7 +66,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
}
|
||||
|
||||
// Use ServiceUri instead of Endpoint since Endpoint already includes /v1
|
||||
var baseUri = _foundryManager.GetServiceUri();
|
||||
var baseUri = _foundryClient.GetServiceUri();
|
||||
if (baseUri == null)
|
||||
{
|
||||
Logger.LogError("[FoundryLocal] Service URI is null");
|
||||
@@ -133,24 +127,25 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
|
||||
private async Task InitializeAsync(CancellationToken cancelationToken = default)
|
||||
{
|
||||
if (_foundryManager != null && _downloadedModels != null && _downloadedModels.Any())
|
||||
if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any())
|
||||
{
|
||||
await _foundryClient.EnsureRunning().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInfo("[FoundryLocal] Initializing provider");
|
||||
_foundryManager ??= await FoundryClient.CreateAsync();
|
||||
_foundryClient ??= await FoundryClient.CreateAsync();
|
||||
|
||||
if (_foundryManager == null)
|
||||
if (_foundryClient == null)
|
||||
{
|
||||
Logger.LogError("[FoundryLocal] Failed to create Foundry client");
|
||||
return;
|
||||
}
|
||||
|
||||
_serviceUrl ??= await _foundryManager.GetServiceUrl();
|
||||
_serviceUrl ??= await _foundryClient.GetServiceUrl();
|
||||
Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
|
||||
|
||||
var cachedModels = await _foundryManager.ListCachedModels();
|
||||
var cachedModels = await _foundryClient.ListCachedModels();
|
||||
Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models");
|
||||
|
||||
List<ModelDetails> downloadedModels = [];
|
||||
@@ -162,7 +157,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
Id = $"fl-{model.Name}",
|
||||
Name = model.Name,
|
||||
Url = $"{UrlPrefix}{model.Name}",
|
||||
Url = $"fl://{model.Name}",
|
||||
Description = $"{model.Name} running locally with Foundry Local",
|
||||
HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
|
||||
SupportedOnQualcomm = true,
|
||||
@@ -178,7 +173,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider
|
||||
{
|
||||
Logger.LogInfo("[FoundryLocal] Checking availability");
|
||||
await InitializeAsync();
|
||||
var available = _foundryManager != null;
|
||||
var available = _foundryClient != null;
|
||||
Logger.LogInfo($"[FoundryLocal] Available: {available}");
|
||||
return available;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ 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 url);
|
||||
IChatClient? GetIChatClient(string modelId);
|
||||
|
||||
string GetIChatClientString(string url);
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace LanguageModelProvider;
|
||||
|
||||
public sealed class LanguageModelService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ILanguageModelProvider> _providersByPrefix;
|
||||
|
||||
public LanguageModelService(IEnumerable<ILanguageModelProvider> providers)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(providers);
|
||||
|
||||
_providersByPrefix = new ConcurrentDictionary<string, ILanguageModelProvider>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(provider.UrlPrefix))
|
||||
{
|
||||
_providersByPrefix[provider.UrlPrefix] = provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LanguageModelService CreateDefault()
|
||||
{
|
||||
return new LanguageModelService(new[]
|
||||
{
|
||||
FoundryLocalModelProvider.Instance,
|
||||
});
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ILanguageModelProvider> Providers => _providersByPrefix.Values.ToArray();
|
||||
|
||||
public bool RegisterProvider(ILanguageModelProvider provider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(provider);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(provider.UrlPrefix))
|
||||
{
|
||||
throw new ArgumentException("Provider must supply a URL prefix.", nameof(provider));
|
||||
}
|
||||
|
||||
_providersByPrefix[provider.UrlPrefix] = provider;
|
||||
return true;
|
||||
}
|
||||
|
||||
public ILanguageModelProvider? GetProviderFor(string? modelReference)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelReference))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var provider in _providersByPrefix.Values)
|
||||
{
|
||||
if (modelReference.StartsWith(provider.UrlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ModelDetails>> GetModelsAsync(bool refresh = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
List<ModelDetails> models = [];
|
||||
|
||||
foreach (var provider in _providersByPrefix.Values)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var providerModels = await provider.GetModelsAsync(refresh, cancellationToken).ConfigureAwait(false);
|
||||
models.AddRange(providerModels);
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
public IChatClient? GetClient(ModelDetails model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var reference = !string.IsNullOrWhiteSpace(model.Url) ? model.Url : model.Id;
|
||||
return GetClient(reference);
|
||||
}
|
||||
|
||||
public IChatClient? GetClient(string? modelReference)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelReference))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var provider = GetProviderFor(modelReference);
|
||||
|
||||
return provider?.GetIChatClient(modelReference);
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,8 @@ 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}"
|
||||
Text="Local" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Border>
|
||||
<!--<Border
|
||||
Grid.Column="2"
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="20"
|
||||
Visibility="{x:Bind ViewModel.ClipboardHasData, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
Visibility="{x:Bind ViewModel.ShowClipboardPreview, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -168,7 +168,8 @@
|
||||
Margin="0,0,4,0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.ShowClipboardHistoryButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="ClipboardHistoryButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
@@ -263,16 +264,17 @@
|
||||
<Button
|
||||
Padding="0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.HasLegalLinks, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<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}"
|
||||
Text="You are using a custom endpoint. Verify all answers." />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<HyperlinkButton
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace AdvancedPaste.Helpers
|
||||
PromptTokens = semanticKernelFormatEvent.PromptTokens;
|
||||
CompletionTokens = semanticKernelFormatEvent.CompletionTokens;
|
||||
ModelName = semanticKernelFormatEvent.ModelName;
|
||||
ProviderType = semanticKernelFormatEvent.ProviderType;
|
||||
ActionChain = semanticKernelFormatEvent.ActionChain;
|
||||
}
|
||||
|
||||
@@ -38,6 +39,8 @@ 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,6 +19,8 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public bool EnableClipboardPreview { get; }
|
||||
|
||||
public IReadOnlyList<AdvancedPasteCustomAction> CustomActions { get; }
|
||||
|
||||
public IReadOnlyList<PasteFormats> AdditionalActions { get; }
|
||||
|
||||
@@ -40,6 +40,8 @@ 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;
|
||||
@@ -53,6 +55,7 @@ namespace AdvancedPaste.Settings
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new PasteAIConfiguration();
|
||||
_additionalActions = [];
|
||||
_customActions = [];
|
||||
@@ -107,6 +110,7 @@ namespace AdvancedPaste.Settings
|
||||
IsAIEnabled = properties.IsAIEnabled;
|
||||
ShowCustomPreview = properties.ShowCustomPreview;
|
||||
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
|
||||
EnableClipboardPreview = properties.EnableClipboardPreview;
|
||||
PasteAIConfiguration = properties.PasteAIConfiguration ?? new PasteAIConfiguration();
|
||||
|
||||
var sourceAdditionalActions = properties.AdditionalActions;
|
||||
@@ -164,6 +168,15 @@ namespace AdvancedPaste.Settings
|
||||
}
|
||||
|
||||
var properties = settings.Properties;
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (legacyCredential is null)
|
||||
{
|
||||
return legacyAdvancedAIConsumed;
|
||||
}
|
||||
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
|
||||
if (configuration is null)
|
||||
@@ -172,30 +185,11 @@ namespace AdvancedPaste.Settings
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
|
||||
bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
|
||||
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (hasLegacyProviders)
|
||||
{
|
||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
||||
}
|
||||
|
||||
PasteAIProviderDefinition openAIProvider = null;
|
||||
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
|
||||
{
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
}
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
@@ -203,16 +197,17 @@ namespace AdvancedPaste.Settings
|
||||
configurationUpdated = true;
|
||||
}
|
||||
|
||||
if (legacyCredential is not null && openAIProvider is not null)
|
||||
if (openAIProvider is not null)
|
||||
{
|
||||
StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
const bool shouldEnableAI = true;
|
||||
bool enabledUpdated = false;
|
||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
||||
if (properties.IsAIEnabled != shouldEnableAI)
|
||||
{
|
||||
properties.IsAIEnabled = true;
|
||||
properties.IsAIEnabled = shouldEnableAI;
|
||||
enabledUpdated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,32 +83,39 @@ 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
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType);
|
||||
// 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);
|
||||
PowerToysTelemetry.Log.WriteEvent(endpointEvent);
|
||||
|
||||
Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}");
|
||||
Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}, DurationMs={durationMs}");
|
||||
|
||||
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 LanguageModelService LanguageModels = LanguageModelService.CreateDefault();
|
||||
private static readonly FoundryLocalModelProvider _modelProvider = FoundryLocalModelProvider.Instance;
|
||||
|
||||
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. Model identifier should be in the format 'fl://model-name'.");
|
||||
aiServiceMessage: "Please select a model in the AI provider settings.");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var chatClient = LanguageModels.GetClient(modelReference);
|
||||
var chatClient = _modelProvider.GetIChatClient(modelReference);
|
||||
if (chatClient is null)
|
||||
{
|
||||
throw new PasteActionException(
|
||||
@@ -85,9 +85,6 @@ 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}
|
||||
@@ -104,7 +101,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider
|
||||
new(ChatRole.User, userMessageContent),
|
||||
};
|
||||
|
||||
var chatOptions = CreateChatOptions(_config?.SystemPrompt, actualModelId);
|
||||
var chatOptions = CreateChatOptions(_config?.SystemPrompt, modelReference);
|
||||
|
||||
progress?.Report(0.1);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -144,7 +145,8 @@ public abstract class KernelServiceBase(
|
||||
|
||||
ChatHistory chatHistory = [];
|
||||
|
||||
chatHistory.AddSystemMessage(runtimeConfig.SystemPrompt);
|
||||
var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
|
||||
chatHistory.AddSystemMessage(systemPrompt);
|
||||
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
|
||||
chatHistory.AddUserMessage(prompt);
|
||||
|
||||
@@ -186,12 +188,20 @@ public abstract class KernelServiceBase(
|
||||
|
||||
private void LogResult(bool cacheUsed, bool isSavedQuery, IEnumerable<ActionChainItem> actionChain, AIServiceUsage usage)
|
||||
{
|
||||
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(cacheUsed, isSavedQuery, usage.PromptTokens, usage.CompletionTokens, AdvancedAIModelName, AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
|
||||
var runtimeConfig = GetRuntimeConfiguration();
|
||||
|
||||
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(
|
||||
cacheUsed,
|
||||
isSavedQuery,
|
||||
usage.PromptTokens,
|
||||
usage.CompletionTokens,
|
||||
AdvancedAIModelName,
|
||||
runtimeConfig.ServiceType.ToString(),
|
||||
AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
|
||||
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||
|
||||
// Log endpoint usage
|
||||
var runtimeConfig = GetRuntimeConfiguration();
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType);
|
||||
var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType, AdvancedAIModelName, isAdvanced: true);
|
||||
PowerToysTelemetry.Log.WriteEvent(endpointEvent);
|
||||
|
||||
var logEvent = new AIServiceFormatEvent(telemetryEvent);
|
||||
|
||||
@@ -359,6 +359,13 @@
|
||||
</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>
|
||||
<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>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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,9 +19,27 @@ public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
|
||||
/// </summary>
|
||||
public string ProviderType { get; set; }
|
||||
|
||||
public AdvancedPasteEndpointUsageEvent(AIServiceType providerType)
|
||||
/// <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)
|
||||
{
|
||||
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 actionChain) : EventBase, IEvent
|
||||
public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string providerType, string actionChain) : EventBase, IEvent
|
||||
{
|
||||
public static string FormatActionChain(IEnumerable<ActionChainItem> actionChain) => FormatActionChain(actionChain.Select(item => item.Format));
|
||||
|
||||
@@ -30,6 +30,8 @@ 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,6 +63,7 @@ namespace AdvancedPaste.ViewModels
|
||||
private ClipboardFormat _availableClipboardFormats;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowClipboardHistoryButton))]
|
||||
private bool _clipboardHistoryEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -76,6 +77,7 @@ namespace AdvancedPaste.ViewModels
|
||||
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
|
||||
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
|
||||
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegalLinks))]
|
||||
private bool _isAllowedByGPO;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -221,10 +223,16 @@ 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 =>
|
||||
@@ -310,6 +318,7 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
||||
OnPropertyChanged(nameof(AIProviders));
|
||||
OnPropertyChanged(nameof(AllowedAIProviders));
|
||||
OnPropertyChanged(nameof(ShowClipboardPreview));
|
||||
|
||||
NotifyActiveProviderChanged();
|
||||
|
||||
@@ -361,6 +370,7 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(PrivacyLinkUri));
|
||||
OnPropertyChanged(nameof(HasTermsLink));
|
||||
OnPropertyChanged(nameof(HasPrivacyLink));
|
||||
OnPropertyChanged(nameof(HasLegalLinks));
|
||||
}
|
||||
|
||||
private void RefreshPasteFormats()
|
||||
|
||||
@@ -6440,12 +6440,10 @@ LRESULT APIENTRY MainWndProc(
|
||||
GetCursorPos(&local_savedCursorPos);
|
||||
}
|
||||
|
||||
HBITMAP hInterimSaveBitmap;
|
||||
HDC hInterimSaveDc;
|
||||
HBITMAP hSaveBitmap;
|
||||
HDC hSaveDc;
|
||||
int copyX, copyY;
|
||||
int copyWidth, copyHeight;
|
||||
// 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;
|
||||
|
||||
if ( LOWORD( wParam ) == IDC_SAVE_CROP )
|
||||
{
|
||||
@@ -6460,55 +6458,51 @@ 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 );
|
||||
|
||||
// Capture the screen before displaying the save dialog
|
||||
hInterimSaveDc = CreateCompatibleDC( hdcScreen );
|
||||
hInterimSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight );
|
||||
SelectObject( hInterimSaveDc, hInterimSaveBitmap );
|
||||
// Translate the viewport selection into coordinates for the 1:1 source
|
||||
// bitmap hdcScreenCompat.
|
||||
int viewportX, viewportY;
|
||||
GetZoomedTopLeftCoordinates(
|
||||
zoomLevel, &cursorPos, &viewportX, width, &viewportY, height );
|
||||
|
||||
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 );
|
||||
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 );
|
||||
|
||||
// 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;
|
||||
@@ -6524,6 +6518,7 @@ 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];
|
||||
@@ -6533,42 +6528,47 @@ LRESULT APIENTRY MainWndProc(
|
||||
_tcscat( targetFilePath, L".png" );
|
||||
}
|
||||
|
||||
// Save image at screen size
|
||||
if( openFileName.nFilterIndex == 1 )
|
||||
if( openFileName.nFilterIndex == 2 )
|
||||
{
|
||||
SavePng( targetFilePath, hInterimSaveBitmap );
|
||||
// Save at actual size.
|
||||
SavePng( targetFilePath, hbmActualSize.get() );
|
||||
}
|
||||
// Save image scaled down to actual size
|
||||
else
|
||||
{
|
||||
int saveWidth = static_cast<int>( copyWidth / zoomLevel );
|
||||
int saveHeight = static_cast<int>( copyHeight / zoomLevel );
|
||||
// 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() );
|
||||
|
||||
hSaveBitmap = CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight );
|
||||
SelectObject( hSaveDc, hSaveBitmap );
|
||||
SetStretchBltMode( hdcZoomed.get(), bltMode );
|
||||
|
||||
StretchBlt( hSaveDc,
|
||||
StretchBlt( hdcZoomed.get(),
|
||||
0, 0,
|
||||
copyWidth, copyHeight,
|
||||
hdcActualSize.get(),
|
||||
0, 0,
|
||||
saveWidth, saveHeight,
|
||||
hInterimSaveDc,
|
||||
0,
|
||||
0,
|
||||
copyWidth, copyHeight,
|
||||
SRCCOPY | CAPTUREBLT );
|
||||
|
||||
SavePng( targetFilePath, hSaveBitmap );
|
||||
SavePng( targetFilePath, hbmZoomed.get() );
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +15,7 @@ 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,15 +26,69 @@ public sealed class ToggleSetting : Setting<bool>
|
||||
|
||||
public override Dictionary<string, object> ToDictionary()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
var items = new List<Dictionary<string, object>>();
|
||||
|
||||
if (!string.IsNullOrEmpty(Label))
|
||||
{
|
||||
{ "type", "Input.Toggle" },
|
||||
{ "title", Label },
|
||||
{ "id", Key },
|
||||
{ "label", Description },
|
||||
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
|
||||
{ "isRequired", IsRequired },
|
||||
{ "errorMessage", ErrorMessage },
|
||||
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" },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores provider-specific configuration overrides so each AI service can keep distinct settings.
|
||||
/// </summary>
|
||||
public class AIProviderConfigurationSnapshot
|
||||
{
|
||||
[JsonPropertyName("model-name")]
|
||||
public string ModelName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("endpoint-url")]
|
||||
public string EndpointUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("api-version")]
|
||||
public string ApiVersion { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("deployment-name")]
|
||||
public string DeploymentName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("model-path")]
|
||||
public string ModelPath { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("system-prompt")]
|
||||
public string SystemPrompt { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("moderation-enabled")]
|
||||
public bool ModerationEnabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -13,56 +13,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
/// </summary>
|
||||
public static class AdvancedPasteMigrationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves legacy provider configuration snapshots into the strongly-typed providers collection.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance to migrate.</param>
|
||||
/// <returns>True if the configuration was modified.</returns>
|
||||
public static bool MigrateLegacyProviderConfigurations(PasteAIConfiguration configuration)
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (configuration.LegacyProviderConfigurations is { Count: > 0 })
|
||||
{
|
||||
foreach (var entry in configuration.LegacyProviderConfigurations)
|
||||
{
|
||||
var result = EnsureProvider(configuration, entry.Key, entry.Value);
|
||||
configurationUpdated |= result.Updated;
|
||||
}
|
||||
|
||||
configuration.LegacyProviderConfigurations = null;
|
||||
}
|
||||
|
||||
configurationUpdated |= EnsureActiveProviderIsValid(configuration);
|
||||
|
||||
return configurationUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures an OpenAI provider exists in the configuration, creating one if necessary.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance.</param>
|
||||
/// <returns>The ensured provider and a flag indicating whether changes were made.</returns>
|
||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureOpenAIProvider(PasteAIConfiguration configuration)
|
||||
{
|
||||
return EnsureProvider(configuration, AIServiceType.OpenAI.ToConfigurationString(), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a provider for the supplied service type exists, optionally applying a legacy snapshot.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration instance.</param>
|
||||
/// <param name="serviceTypeKey">The persisted service type key.</param>
|
||||
/// <param name="snapshot">An optional snapshot containing legacy values.</param>
|
||||
/// <returns>The ensured provider and whether the configuration was updated.</returns>
|
||||
public static (PasteAIProviderDefinition Provider, bool Updated) EnsureProvider(PasteAIConfiguration configuration, string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
@@ -71,101 +27,45 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
configuration.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
var normalizedServiceType = NormalizeServiceType(serviceTypeKey);
|
||||
var existingProvider = configuration.Providers.FirstOrDefault(provider => string.Equals(provider.ServiceType, normalizedServiceType, StringComparison.OrdinalIgnoreCase));
|
||||
bool configurationUpdated = false;
|
||||
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(normalizedServiceType, snapshot);
|
||||
existingProvider = CreateProvider(serviceTypeKey);
|
||||
configuration.Providers.Add(existingProvider);
|
||||
configurationUpdated = true;
|
||||
}
|
||||
else if (snapshot is not null)
|
||||
{
|
||||
configurationUpdated |= ApplySnapshot(existingProvider, snapshot);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
configurationUpdated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
||||
updated |= EnsureActiveProviderIsValid(configuration, existingProvider);
|
||||
|
||||
return (existingProvider, configurationUpdated);
|
||||
return (existingProvider, updated);
|
||||
}
|
||||
|
||||
private static string NormalizeServiceType(string serviceTypeKey)
|
||||
{
|
||||
var serviceType = serviceTypeKey.ToAIServiceType();
|
||||
return serviceType.ToConfigurationString();
|
||||
}
|
||||
|
||||
private static PasteAIProviderDefinition CreateProvider(string serviceTypeKey, AIProviderConfigurationSnapshot snapshot)
|
||||
/// <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 = !string.IsNullOrWhiteSpace(snapshot?.ModelName) ? snapshot.ModelName : PasteAIProviderDefaults.GetDefaultModelName(serviceType),
|
||||
EndpointUrl = snapshot?.EndpointUrl ?? string.Empty,
|
||||
ApiVersion = snapshot?.ApiVersion ?? string.Empty,
|
||||
DeploymentName = snapshot?.DeploymentName ?? string.Empty,
|
||||
ModelPath = snapshot?.ModelPath ?? string.Empty,
|
||||
SystemPrompt = snapshot?.SystemPrompt ?? string.Empty,
|
||||
ModerationEnabled = snapshot?.ModerationEnabled ?? true,
|
||||
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 ApplySnapshot(PasteAIProviderDefinition provider, AIProviderConfigurationSnapshot snapshot)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ModelName) && !string.Equals(provider.ModelName, snapshot.ModelName, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ModelName = snapshot.ModelName;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.EndpointUrl) && !string.Equals(provider.EndpointUrl, snapshot.EndpointUrl, StringComparison.Ordinal))
|
||||
{
|
||||
provider.EndpointUrl = snapshot.EndpointUrl;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ApiVersion) && !string.Equals(provider.ApiVersion, snapshot.ApiVersion, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ApiVersion = snapshot.ApiVersion;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.DeploymentName) && !string.Equals(provider.DeploymentName, snapshot.DeploymentName, StringComparison.Ordinal))
|
||||
{
|
||||
provider.DeploymentName = snapshot.DeploymentName;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.ModelPath) && !string.Equals(provider.ModelPath, snapshot.ModelPath, StringComparison.Ordinal))
|
||||
{
|
||||
provider.ModelPath = snapshot.ModelPath;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SystemPrompt) && !string.Equals(provider.SystemPrompt, snapshot.SystemPrompt, StringComparison.Ordinal))
|
||||
{
|
||||
provider.SystemPrompt = snapshot.SystemPrompt;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (provider.ModerationEnabled != snapshot.ModerationEnabled)
|
||||
{
|
||||
provider.ModerationEnabled = snapshot.ModerationEnabled;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static bool EnsureActiveProviderIsValid(PasteAIConfiguration configuration, PasteAIProviderDefinition preferredProvider = null)
|
||||
{
|
||||
if (configuration?.Providers is null || configuration.Providers.Count == 0)
|
||||
|
||||
@@ -27,58 +27,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new();
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool IsAIEnabled { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, JsonElement> ExtensionData
|
||||
{
|
||||
get => _extensionData;
|
||||
set
|
||||
{
|
||||
_extensionData = value;
|
||||
|
||||
if (_extensionData != null && _extensionData.TryGetValue("IsOpenAIEnabled", out var legacyElement) && legacyElement.ValueKind == JsonValueKind.Object && legacyElement.TryGetProperty("value", out var valueElement))
|
||||
{
|
||||
IsAIEnabled = valueElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => IsAIEnabled,
|
||||
};
|
||||
|
||||
_extensionData.Remove("IsOpenAIEnabled");
|
||||
}
|
||||
|
||||
if (_extensionData != null && _extensionData.TryGetValue("IsAdvancedAIEnabled", out var legacyAdvancedElement))
|
||||
{
|
||||
bool? legacyValue = legacyAdvancedElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Object when legacyAdvancedElement.TryGetProperty("value", out var advancedValueElement) => advancedValueElement.ValueKind switch
|
||||
{
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => null,
|
||||
},
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (legacyValue.HasValue)
|
||||
{
|
||||
LegacyAdvancedAIEnabled = legacyValue.Value;
|
||||
}
|
||||
|
||||
_extensionData.Remove("IsAdvancedAIEnabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, JsonElement> _extensionData;
|
||||
private bool? _legacyAdvancedAIEnabled;
|
||||
|
||||
[JsonPropertyName("IsAdvancedAIEnabled")]
|
||||
@@ -121,6 +76,9 @@ 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,8 +20,6 @@ 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;
|
||||
|
||||
@@ -39,21 +37,6 @@ 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
|
||||
{
|
||||
|
||||
@@ -168,6 +168,9 @@
|
||||
</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"
|
||||
@@ -426,12 +429,11 @@
|
||||
<!-- 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}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">900</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxHeight">700</x:Double>
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage, IDisposable
|
||||
{
|
||||
private readonly ObservableCollection<ModelDetails> _foundryCachedModels = new();
|
||||
private readonly ObservableCollection<FoundryDownloadableModel> _foundryDownloadableModels = new();
|
||||
private CancellationTokenSource _foundryModelLoadCts;
|
||||
private bool _suppressFoundrySelectionChanged;
|
||||
private bool _isFoundryLocalAvailable;
|
||||
@@ -57,7 +56,6 @@ 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;
|
||||
}
|
||||
@@ -469,7 +467,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
var cachedModels = cachedModelsEnumerable?.ToList() ?? new List<ModelDetails>();
|
||||
|
||||
UpdateFoundryCollections(cachedModels, []);
|
||||
UpdateFoundryCollections(cachedModels);
|
||||
ShowFoundryAvailableState();
|
||||
RestoreFoundrySelection(cachedModels);
|
||||
}
|
||||
@@ -538,7 +536,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
UpdateFoundrySaveButtonState();
|
||||
}
|
||||
|
||||
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels, IReadOnlyCollection<ModelDetails> catalogModels)
|
||||
private void UpdateFoundryCollections(IReadOnlyCollection<ModelDetails> cachedModels)
|
||||
{
|
||||
_foundryCachedModels.Clear();
|
||||
|
||||
@@ -547,20 +545,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
_foundryCachedModels.Add(model);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
var cachedReferences = new HashSet<string>(_foundryCachedModels.Select(m => m.Name), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void RestoreFoundrySelection(IReadOnlyCollection<ModelDetails> cachedModels)
|
||||
@@ -576,9 +561,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(currentModelReference))
|
||||
{
|
||||
var normalizedReference = NormalizeFoundryModelReference(currentModelReference);
|
||||
matchingModel = cachedModels.FirstOrDefault(model =>
|
||||
string.Equals(NormalizeFoundryModelReference(model.Url ?? model.Name), normalizedReference, StringComparison.OrdinalIgnoreCase));
|
||||
string.Equals(model.Name, currentModelReference, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is null)
|
||||
@@ -608,7 +592,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||
{
|
||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(matchingModel.Url ?? matchingModel.Name);
|
||||
ViewModel.PasteAIProviderDraft.ModelName = matchingModel.Name;
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is not null)
|
||||
@@ -620,19 +604,6 @@ 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)
|
||||
@@ -656,7 +627,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isFoundryLocalAvailable || _foundryDownloadableModels.Any(model => model.IsDownloading))
|
||||
if (!_isFoundryLocalAvailable)
|
||||
{
|
||||
PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false;
|
||||
return;
|
||||
@@ -677,7 +648,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
if (ViewModel?.PasteAIProviderDraft is not null)
|
||||
{
|
||||
ViewModel.PasteAIProviderDraft.ModelName = NormalizeFoundryModelReference(selectedModel.Url ?? selectedModel.Name);
|
||||
ViewModel.PasteAIProviderDraft.ModelName = selectedModel.Name;
|
||||
}
|
||||
|
||||
if (FoundryLocalPicker is not null)
|
||||
|
||||
@@ -199,10 +199,8 @@
|
||||
<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,9 +35,6 @@ 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; }
|
||||
|
||||
@@ -132,8 +129,6 @@ 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;
|
||||
@@ -157,23 +152,10 @@ 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))
|
||||
{
|
||||
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)
|
||||
if (double.IsNaN(latitude) || double.IsNaN(longitude) || (latitude == 0 && longitude == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +165,6 @@ 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)
|
||||
{
|
||||
@@ -214,37 +195,6 @@ 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,6 +283,7 @@
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsCursorWrapSettingsExpander"
|
||||
x:Uid="MouseUtils_CursorWrap_ActivationShortcut"
|
||||
@@ -294,18 +295,7 @@
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="MouseUtils_AutoActivate" IsChecked="{x:Bind ViewModel.CursorWrapAutoActivate, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</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">
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
|
||||
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableWrapDuringDrag" IsChecked="{x:Bind ViewModel.CursorWrapDisableWrapDuringDrag, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
|
||||
@@ -285,6 +285,9 @@
|
||||
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"
|
||||
@@ -299,7 +302,11 @@
|
||||
x:Uid="Shell_MouseUtilities"
|
||||
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
|
||||
AutomationProperties.AutomationId="MouseUtilitiesNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}" />
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseUtils.png}">
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge Style="{StaticResource NewInfoBadge}" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem
|
||||
x:Name="MouseWithoutBordersNavigationItem"
|
||||
x:Uid="Shell_MouseWithoutBorders"
|
||||
|
||||
@@ -648,10 +648,10 @@ Please review the placeholder content that represents the final terms and usage
|
||||
<value>Use built-in functions to handle complex tasks. Token consumption may increase.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Show what's currently on your Clipboard and access your Clipboard history</value>
|
||||
<value>Access Clipboard History</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Clipboard_History_Enabled_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Clipboard History shows a list of previously copied items.</value>
|
||||
<value>View and select previously copied items</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Actions</value>
|
||||
@@ -2685,10 +2685,7 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<value>Wrap the mouse cursor between monitor edges</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut.Header" xml:space="preserve">
|
||||
<value>Activation shortcut</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Description.Text" xml:space="preserve">
|
||||
<value>Hotkey to toggle cursor wrapping on/off</value>
|
||||
<value>Activation and behavior</value>
|
||||
</data>
|
||||
<data name="MouseUtils_CursorWrap_ActivationShortcut_Button.Content" xml:space="preserve">
|
||||
<value>Set shortcut</value>
|
||||
@@ -4601,6 +4598,10 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<value>Automatically close the window after it loses focus</value>
|
||||
<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>
|
||||
@@ -5743,4 +5744,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<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>
|
||||
@@ -191,6 +191,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (legacyCredential is null)
|
||||
{
|
||||
if (legacyAdvancedAIConsumed)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = properties.PasteAIConfiguration;
|
||||
if (configuration is null)
|
||||
{
|
||||
@@ -198,28 +210,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
properties.PasteAIConfiguration = configuration;
|
||||
}
|
||||
|
||||
bool hasLegacyProviders = configuration.LegacyProviderConfigurations is { Count: > 0 };
|
||||
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
|
||||
|
||||
if (!hasLegacyProviders && legacyCredential is null && !legacyAdvancedAIConsumed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool configurationUpdated = false;
|
||||
|
||||
if (hasLegacyProviders)
|
||||
{
|
||||
configurationUpdated |= AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(configuration);
|
||||
}
|
||||
|
||||
PasteAIProviderDefinition openAIProvider = null;
|
||||
if (legacyCredential is not null || hasLegacyProviders || legacyAdvancedAIConsumed)
|
||||
{
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
}
|
||||
var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
|
||||
PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
|
||||
configurationUpdated |= ensureResult.Updated;
|
||||
|
||||
if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
|
||||
{
|
||||
@@ -233,10 +228,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
RemoveLegacyOpenAICredential();
|
||||
}
|
||||
|
||||
const bool shouldEnableAI = true;
|
||||
bool enabledChanged = false;
|
||||
if (!properties.IsAIEnabled && legacyCredential is not null)
|
||||
if (properties.IsAIEnabled != shouldEnableAI)
|
||||
{
|
||||
properties.IsAIEnabled = true;
|
||||
properties.IsAIEnabled = shouldEnableAI;
|
||||
enabledChanged = true;
|
||||
}
|
||||
|
||||
@@ -546,6 +542,19 @@ 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])
|
||||
@@ -1204,6 +1213,12 @@ 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))
|
||||
{
|
||||
@@ -1228,11 +1243,6 @@ 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>();
|
||||
|
||||
@@ -1388,8 +1398,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal)
|
||||
|| string.Equals(e.PropertyName, nameof(PasteAIConfiguration.UseSharedCredentials), StringComparison.Ordinal))
|
||||
if (string.Equals(e.PropertyName, nameof(PasteAIConfiguration.ActiveProviderId), StringComparison.Ordinal))
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
}
|
||||
@@ -1406,15 +1415,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
|
||||
|
||||
bool configurationUpdated = AdvancedPasteMigrationHelper.MigrateLegacyProviderConfigurations(pasteConfig);
|
||||
|
||||
SubscribeToPasteAIProviders(pasteConfig);
|
||||
|
||||
if (configurationUpdated)
|
||||
{
|
||||
SaveAndNotifySettings();
|
||||
OnPropertyChanged(nameof(PasteAIConfiguration));
|
||||
}
|
||||
}
|
||||
|
||||
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)
|
||||
|
||||
@@ -145,6 +145,7 @@ 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);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>../../../src/</AdditionalIncludeDirectories>
|
||||
<TreatAngleIncludeAsExternal>true</TreatAngleIncludeAsExternal>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
@@ -37,10 +38,6 @@
|
||||
</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" />
|
||||
@@ -61,8 +58,6 @@
|
||||
</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,7 +8,6 @@
|
||||
<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" />
|
||||
@@ -28,8 +27,6 @@
|
||||
<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,50 +1,53 @@
|
||||
#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)
|
||||
{
|
||||
std::string reportFilename{ "PowerToysReport_" };
|
||||
reportFilename += timeutil::format_as_local("%F-%H-%M-%S", timeutil::now());
|
||||
reportFilename += ".zip";
|
||||
const auto reportFilename{
|
||||
std::format("PowerToysReport_{0}.zip",
|
||||
timeutil::format_as_local("%F-%H-%M-%S", timeutil::now()))
|
||||
};
|
||||
const auto finalReportFullPath{ zipPath / reportFilename };
|
||||
|
||||
auto tmpZipPath = std::filesystem::temp_directory_path();
|
||||
tmpZipPath /= reportFilename;
|
||||
const auto tempReportFilename{ reportFilename + ".tmp" };
|
||||
const auto tempReportFullPath{ zipPath / tempReportFilename };
|
||||
|
||||
struct zip_t* zip = zip_open(tmpZipPath.string().c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
||||
if (!zip)
|
||||
// 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))
|
||||
{
|
||||
printf("Cannot open zip.");
|
||||
throw -1;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
zip_close(zip);
|
||||
|
||||
std::error_code err;
|
||||
std::filesystem::copy(tmpZipPath, zipPath, err);
|
||||
std::error_code err{};
|
||||
std::filesystem::rename(tempReportFullPath, finalReportFullPath, err);
|
||||
if (err.value() != 0)
|
||||
{
|
||||
wprintf_s(L"Failed to copy %s. Error code: %d\n", tmpZipPath.c_str(), err.value());
|
||||
wprintf_s(L"Failed to rename %s. Error code: %d\n", tempReportFullPath.native().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