mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-07 04:46:56 +01:00
Compare commits
4 Commits
shawn/disa
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16cce864a5 | ||
|
|
b72d8bb46e | ||
|
|
25e1a5414b | ||
|
|
dc94e28212 |
@@ -9,7 +9,7 @@
|
||||
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.5" />
|
||||
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.17" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
|
||||
<PackageVersion Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
@@ -45,7 +45,7 @@
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.8" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.46.0" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
@@ -71,7 +71,7 @@
|
||||
<PackageVersion Include="NLog" Version="5.2.8" />
|
||||
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
|
||||
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
|
||||
<PackageVersion Include="OpenAI" Version="2.0.0" />
|
||||
<PackageVersion Include="OpenAI" Version="2.2.0-beta.4" />
|
||||
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
|
||||
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
|
||||
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
|
||||
|
||||
@@ -14,9 +14,11 @@ using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.Services.OpenAI;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.UnitTests.Mocks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.ServicesTests;
|
||||
@@ -130,10 +132,11 @@ public sealed class AIServiceBatchIntegrationTests
|
||||
|
||||
private static async Task<DataPackage> GetOutputDataPackageAsync(BatchTestInput batchTestInput, PasteFormats format)
|
||||
{
|
||||
Mock<IUserSettings> userSettings = new();
|
||||
VaultCredentialsProvider credentialsProvider = new();
|
||||
PromptModerationService promptModerationService = new(credentialsProvider);
|
||||
PromptModerationService promptModerationService = new(userSettings.Object, credentialsProvider);
|
||||
NoOpProgress progress = new();
|
||||
CustomTextTransformService customTextTransformService = new(credentialsProvider, promptModerationService);
|
||||
CustomTextTransformService customTextTransformService = new(userSettings.Object, credentialsProvider, promptModerationService);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
@@ -142,7 +145,7 @@ public sealed class AIServiceBatchIntegrationTests
|
||||
|
||||
case PasteFormats.KernelQuery:
|
||||
var clipboardData = DataPackageHelpers.CreateFromText(batchTestInput.Clipboard).GetView();
|
||||
KernelService kernelService = new(new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, customTextTransformService);
|
||||
KernelService kernelService = new(userSettings.Object, new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, customTextTransformService);
|
||||
return await kernelService.TransformClipboardAsync(batchTestInput.Prompt, clipboardData, isSavedQuery: false, CancellationToken.None, progress);
|
||||
|
||||
default:
|
||||
|
||||
@@ -12,10 +12,12 @@ using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.Services.OpenAI;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.Telemetry;
|
||||
using AdvancedPaste.UnitTests.Mocks;
|
||||
using AdvancedPaste.UnitTests.Utils;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.ServicesTests;
|
||||
@@ -29,14 +31,17 @@ public sealed class KernelServiceIntegrationTests : IDisposable
|
||||
private const string StandardImageFile = "image_with_text_example.png";
|
||||
private KernelService _kernelService;
|
||||
private AdvancedPasteEventListener _eventListener;
|
||||
private Mock<IUserSettings> _userSettings;
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
_userSettings = new();
|
||||
VaultCredentialsProvider credentialsProvider = new();
|
||||
PromptModerationService promptModerationService = new(credentialsProvider);
|
||||
PromptModerationService promptModerationService = new(_userSettings.Object, credentialsProvider);
|
||||
CustomTextTransformService customTextTransformService = new(_userSettings.Object, credentialsProvider, promptModerationService);
|
||||
|
||||
_kernelService = new KernelService(new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, new CustomTextTransformService(credentialsProvider, promptModerationService));
|
||||
_kernelService = new KernelService(_userSettings.Object, new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, customTextTransformService);
|
||||
_eventListener = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -153,49 +153,60 @@
|
||||
x:FieldModifier="public"
|
||||
TabIndex="0">
|
||||
<controls:PromptBox.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="AIMistakeNote" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="4,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink
|
||||
x:Name="TermsHyperlink"
|
||||
NavigateUri="https://openai.com/policies/terms-of-use"
|
||||
TabIndex="3">
|
||||
<Run x:Uid="TermsLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/terms-of-use" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
ToolTipService.ToolTip="">
|
||||
<Run x:Uid="AIFooterSeparator" Foreground="{ThemeResource TextFillColorSecondaryBrush}">|</Run>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink NavigateUri="https://openai.com/policies/privacy-policy" TabIndex="3">
|
||||
<Run x:Uid="PrivacyLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/privacy-policy" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Vertical" Spacing="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="AIMistakeNote" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="4,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink
|
||||
x:Name="TermsHyperlink"
|
||||
NavigateUri="https://openai.com/policies/terms-of-use"
|
||||
TabIndex="3">
|
||||
<Run x:Uid="TermsLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/terms-of-use" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
ToolTipService.ToolTip="">
|
||||
<Run x:Uid="AIFooterSeparator" Foreground="{ThemeResource TextFillColorSecondaryBrush}">|</Run>
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Hyperlink NavigateUri="https://openai.com/policies/privacy-policy" TabIndex="3">
|
||||
<Run x:Uid="PrivacyLink" />
|
||||
</Hyperlink>
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock Text="https://openai.com/policies/privacy-policy" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Visibility="{x:Bind ViewModel.IsLocalModelMode, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}">
|
||||
<Run x:Uid="CustomAIMistakeNote" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:PromptBox.Footer>
|
||||
</controls:PromptBox>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -22,19 +23,24 @@ using Windows.System;
|
||||
|
||||
namespace AdvancedPaste.Pages
|
||||
{
|
||||
public sealed partial class MainPage : Page
|
||||
public sealed partial class MainPage : Page, INotifyPropertyChanged
|
||||
{
|
||||
private readonly ObservableCollection<ClipboardItem> clipboardHistory;
|
||||
private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
|
||||
private (VirtualKey Key, DateTime Timestamp) _lastKeyEvent = (VirtualKey.None, DateTime.MinValue);
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
|
||||
public bool IsLocalModelModeVisible => ViewModel?.IsLocalModelMode == true;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
clipboardHistory = new ObservableCollection<ClipboardItem>();
|
||||
|
||||
@@ -42,6 +48,19 @@ namespace AdvancedPaste.Pages
|
||||
Clipboard.HistoryChanged += LoadClipboardHistoryEvent;
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(OptionsViewModel.IsLocalModelMode))
|
||||
{
|
||||
OnPropertyChanged(nameof(IsLocalModelModeVisible));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private void LoadClipboardHistoryEvent(object sender, object e)
|
||||
{
|
||||
Task.Run(() =>
|
||||
|
||||
@@ -14,6 +14,16 @@ namespace AdvancedPaste.Settings
|
||||
{
|
||||
public bool IsAdvancedAIEnabled { get; }
|
||||
|
||||
public AdvancedPasteAIMode AIMode { get; }
|
||||
|
||||
public bool IsLocalModelMode { get; }
|
||||
|
||||
public string CustomEndpoint { get; }
|
||||
|
||||
public string CustomModelName { get; }
|
||||
|
||||
public bool DisableModeration { get; }
|
||||
|
||||
public bool ShowCustomPreview { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
@@ -35,6 +35,16 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool IsAdvancedAIEnabled { get; private set; }
|
||||
|
||||
public AdvancedPasteAIMode AIMode { get; private set; }
|
||||
|
||||
public bool IsLocalModelMode => AIMode == AdvancedPasteAIMode.LocalModel;
|
||||
|
||||
public string CustomEndpoint { get; private set; }
|
||||
|
||||
public string CustomModelName { get; private set; }
|
||||
|
||||
public bool DisableModeration { get; private set; }
|
||||
|
||||
public bool ShowCustomPreview { get; private set; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
@@ -48,6 +58,10 @@ namespace AdvancedPaste.Settings
|
||||
_settingsUtils = new SettingsUtils(fileSystem);
|
||||
|
||||
IsAdvancedAIEnabled = false;
|
||||
AIMode = AdvancedPasteAIMode.Disabled;
|
||||
CustomEndpoint = string.Empty;
|
||||
CustomModelName = string.Empty;
|
||||
DisableModeration = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
_additionalActions = [];
|
||||
@@ -99,6 +113,34 @@ namespace AdvancedPaste.Settings
|
||||
var properties = settings.Properties;
|
||||
|
||||
IsAdvancedAIEnabled = properties.IsAdvancedAIEnabled;
|
||||
|
||||
// Handle backwards compatibility for AIMode
|
||||
if (properties.AIMode == AdvancedPasteAIMode.Disabled)
|
||||
{
|
||||
// Check if user has custom endpoint/model configured (local model mode)
|
||||
if (!string.IsNullOrWhiteSpace(properties.CustomEndpoint) || !string.IsNullOrWhiteSpace(properties.CustomModelName))
|
||||
{
|
||||
AIMode = AdvancedPasteAIMode.LocalModel;
|
||||
}
|
||||
|
||||
// Check if user has OpenAI key configured
|
||||
else if (IsOpenAIKeyConfigured())
|
||||
{
|
||||
AIMode = AdvancedPasteAIMode.OpenAI;
|
||||
}
|
||||
else
|
||||
{
|
||||
AIMode = AdvancedPasteAIMode.Disabled;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AIMode = properties.AIMode;
|
||||
}
|
||||
|
||||
CustomEndpoint = properties.CustomEndpoint;
|
||||
CustomModelName = properties.CustomModelName;
|
||||
DisableModeration = properties.DisableModeration;
|
||||
ShowCustomPreview = properties.ShowCustomPreview;
|
||||
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
|
||||
|
||||
@@ -144,6 +186,20 @@ namespace AdvancedPaste.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsOpenAIKeyConfigured()
|
||||
{
|
||||
try
|
||||
{
|
||||
var vault = new Windows.Security.Credentials.PasswordVault();
|
||||
vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
@@ -3,54 +3,67 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ClientModel;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.Telemetry;
|
||||
using Azure;
|
||||
using Azure.AI.OpenAI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using OpenAI;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace AdvancedPaste.Services.OpenAI;
|
||||
|
||||
public sealed class CustomTextTransformService(IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService) : ICustomTextTransformService
|
||||
public sealed class CustomTextTransformService(IUserSettings userSettings, IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService) : ICustomTextTransformService
|
||||
{
|
||||
private const string ModelName = "gpt-3.5-turbo-instruct";
|
||||
private readonly IUserSettings _userSettings = userSettings;
|
||||
|
||||
private string ModelName => string.IsNullOrWhiteSpace(_userSettings.CustomModelName) ? "gpt-3.5-turbo-instruct" : _userSettings.CustomModelName;
|
||||
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
|
||||
private readonly IPromptModerationService _promptModerationService = promptModerationService;
|
||||
|
||||
private async Task<Completions> GetAICompletionAsync(string systemInstructions, string userMessage, CancellationToken cancellationToken)
|
||||
private async Task<ChatCompletion> GetAICompletionAsync(string systemInstructions, string userMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var fullPrompt = systemInstructions + "\n\n" + userMessage;
|
||||
|
||||
await _promptModerationService.ValidateAsync(fullPrompt, cancellationToken);
|
||||
|
||||
OpenAIClient azureAIClient = new(_aiCredentialsProvider.Key);
|
||||
OpenAIClientOptions clientOptions = new();
|
||||
if (!string.IsNullOrWhiteSpace(_userSettings.CustomEndpoint))
|
||||
{
|
||||
if (!Uri.TryCreate(_userSettings.CustomEndpoint, UriKind.Absolute, out var endpoint))
|
||||
{
|
||||
throw new ArgumentException($"Invalid custom endpoint URL: '{_userSettings.CustomEndpoint}'. Please ensure the URL includes the protocol (e.g., https://your-server.com/api) and is properly formatted.");
|
||||
}
|
||||
|
||||
var response = await azureAIClient.GetCompletionsAsync(
|
||||
clientOptions.Endpoint = endpoint;
|
||||
}
|
||||
|
||||
OpenAIClient openAIClient = new(new ApiKeyCredential(_aiCredentialsProvider.Key), clientOptions);
|
||||
|
||||
var response = await openAIClient.GetChatClient(ModelName).CompleteChatAsync(
|
||||
[
|
||||
new SystemChatMessage(systemInstructions),
|
||||
new UserChatMessage(userMessage)
|
||||
],
|
||||
new()
|
||||
{
|
||||
DeploymentName = ModelName,
|
||||
Prompts =
|
||||
{
|
||||
fullPrompt,
|
||||
},
|
||||
Temperature = 0.01F,
|
||||
MaxTokens = 2000,
|
||||
MaxOutputTokenCount = 2000,
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
if (response.Value.Choices[0].FinishReason == "length")
|
||||
if (response.Value.FinishReason == ChatFinishReason.Length)
|
||||
{
|
||||
Logger.LogDebug("Cut off due to length constraints");
|
||||
}
|
||||
|
||||
return response;
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
public async Task<string> TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
@@ -85,13 +98,13 @@ Output:
|
||||
var response = await GetAICompletionAsync(systemInstructions, userMessage, cancellationToken);
|
||||
|
||||
var usage = response.Usage;
|
||||
AdvancedPasteGenerateCustomFormatEvent telemetryEvent = new(usage.PromptTokens, usage.CompletionTokens, ModelName);
|
||||
AdvancedPasteGenerateCustomFormatEvent telemetryEvent = new(usage.InputTokenCount, usage.OutputTokenCount, ModelName);
|
||||
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
|
||||
var logEvent = new AIServiceFormatEvent(telemetryEvent);
|
||||
|
||||
Logger.LogDebug($"{nameof(TransformTextAsync)} complete; {logEvent.ToJsonString()}");
|
||||
|
||||
return response.Choices[0].Text;
|
||||
return response.Content[0].Text;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -106,7 +119,7 @@ Output:
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PasteActionException(ErrorHelpers.TranslateErrorText((ex as RequestFailedException)?.Status ?? -1), ex);
|
||||
throw new PasteActionException(ErrorHelpers.TranslateErrorText((ex as ClientResultException)?.Status ?? -1), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,25 @@
|
||||
// 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.Generic;
|
||||
|
||||
using AdvancedPaste.Models;
|
||||
using Azure.AI.OpenAI;
|
||||
using AdvancedPaste.Settings;
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using ChatTokenUsage = OpenAI.Chat.ChatTokenUsage;
|
||||
|
||||
namespace AdvancedPaste.Services.OpenAI;
|
||||
|
||||
public sealed class KernelService(IKernelQueryCacheService queryCacheService, IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService, ICustomTextTransformService customTextTransformService) :
|
||||
public sealed class KernelService(IUserSettings userSettings, IKernelQueryCacheService queryCacheService, IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService, ICustomTextTransformService customTextTransformService) :
|
||||
KernelServiceBase(queryCacheService, promptModerationService, customTextTransformService)
|
||||
{
|
||||
private readonly IUserSettings _userSettings = userSettings;
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
|
||||
|
||||
protected override string ModelName => "gpt-4o";
|
||||
protected override string ModelName => string.IsNullOrWhiteSpace(_userSettings.CustomModelName) ? "gpt-4o" : _userSettings.CustomModelName;
|
||||
|
||||
protected override PromptExecutionSettings PromptExecutionSettings =>
|
||||
new OpenAIPromptExecutionSettings()
|
||||
@@ -25,10 +29,25 @@ public sealed class KernelService(IKernelQueryCacheService queryCacheService, IA
|
||||
Temperature = 0.01,
|
||||
};
|
||||
|
||||
protected override void AddChatCompletionService(IKernelBuilder kernelBuilder) => kernelBuilder.AddOpenAIChatCompletion(ModelName, _aiCredentialsProvider.Key);
|
||||
protected override void AddChatCompletionService(IKernelBuilder kernelBuilder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_userSettings.CustomEndpoint))
|
||||
{
|
||||
kernelBuilder.AddOpenAIChatCompletion(ModelName, _aiCredentialsProvider.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Uri.TryCreate(_userSettings.CustomEndpoint, UriKind.Absolute, out var endpoint))
|
||||
{
|
||||
throw new ArgumentException($"Invalid custom endpoint URL: '{_userSettings.CustomEndpoint}'. Please ensure the URL includes the protocol (e.g., https://your-server.com/api) and is properly formatted.");
|
||||
}
|
||||
|
||||
kernelBuilder.AddOpenAIChatCompletion(ModelName, endpoint, _aiCredentialsProvider.Key);
|
||||
}
|
||||
}
|
||||
|
||||
protected override AIServiceUsage GetAIServiceUsage(ChatMessageContent chatMessage) =>
|
||||
chatMessage.Metadata?.GetValueOrDefault("Usage") is CompletionsUsage completionsUsage
|
||||
? new(PromptTokens: completionsUsage.PromptTokens, CompletionTokens: completionsUsage.CompletionTokens)
|
||||
chatMessage.Metadata?.GetValueOrDefault("Usage") is ChatTokenUsage completionsUsage
|
||||
? new(PromptTokens: completionsUsage.InputTokenCount, CompletionTokens: completionsUsage.OutputTokenCount)
|
||||
: AIServiceUsage.None;
|
||||
}
|
||||
|
||||
@@ -2,28 +2,50 @@
|
||||
// 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.ClientModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Models;
|
||||
using AdvancedPaste.Settings;
|
||||
using ManagedCommon;
|
||||
using OpenAI;
|
||||
using OpenAI.Moderations;
|
||||
|
||||
namespace AdvancedPaste.Services.OpenAI;
|
||||
|
||||
public sealed class PromptModerationService(IAICredentialsProvider aiCredentialsProvider) : IPromptModerationService
|
||||
public sealed class PromptModerationService(IUserSettings userSettings, IAICredentialsProvider aiCredentialsProvider) : IPromptModerationService
|
||||
{
|
||||
private readonly IUserSettings _userSettings = userSettings;
|
||||
|
||||
private const string ModelName = "omni-moderation-latest";
|
||||
|
||||
private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
|
||||
|
||||
public async Task ValidateAsync(string fullPrompt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_userSettings.DisableModeration)
|
||||
{
|
||||
Logger.LogDebug($"{nameof(PromptModerationService)}.{nameof(ValidateAsync)} skipped; moderation is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ModerationClient moderationClient = new(ModelName, _aiCredentialsProvider.Key);
|
||||
OpenAIClientOptions clientOptions = new();
|
||||
if (!string.IsNullOrWhiteSpace(_userSettings.CustomEndpoint))
|
||||
{
|
||||
if (!Uri.TryCreate(_userSettings.CustomEndpoint, UriKind.Absolute, out var endpoint))
|
||||
{
|
||||
throw new ArgumentException($"Invalid custom endpoint URL: {_userSettings.CustomEndpoint}");
|
||||
}
|
||||
|
||||
clientOptions.Endpoint = endpoint;
|
||||
}
|
||||
|
||||
ModerationClient moderationClient = new(ModelName, new(_aiCredentialsProvider.Key), clientOptions);
|
||||
var moderationClientResult = await moderationClient.ClassifyTextAsync(fullPrompt, cancellationToken);
|
||||
var moderationResult = moderationClientResult.Value;
|
||||
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="AIMistakeNote.Text" xml:space="preserve">
|
||||
<value>AI can make mistakes.</value>
|
||||
</data>
|
||||
<data name="CustomAIMistakeNote.Text" xml:space="preserve">
|
||||
<value>You are using a custom model endpoint please verify all answers.</value>
|
||||
</data>
|
||||
<data name="ClipboardEmptyWarning" xml:space="preserve">
|
||||
<value>Clipboard does not contain any usable formats</value>
|
||||
</data>
|
||||
|
||||
@@ -85,6 +85,14 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public bool IsAdvancedAIEnabled => IsCustomAIServiceEnabled && _userSettings.IsAdvancedAIEnabled;
|
||||
|
||||
public bool IsLocalModelMode => _userSettings.IsLocalModelMode;
|
||||
|
||||
public string CustomEndpoint => _userSettings.CustomEndpoint;
|
||||
|
||||
public string CustomModelName => _userSettings.CustomModelName;
|
||||
|
||||
public bool DisableModeration => _userSettings.DisableModeration;
|
||||
|
||||
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
||||
|
||||
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
||||
@@ -167,6 +175,10 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(ClipboardHasDataForCustomAI));
|
||||
OnPropertyChanged(nameof(IsCustomAIAvailable));
|
||||
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
||||
OnPropertyChanged(nameof(IsLocalModelMode));
|
||||
OnPropertyChanged(nameof(CustomEndpoint));
|
||||
OnPropertyChanged(nameof(CustomModelName));
|
||||
OnPropertyChanged(nameof(DisableModeration));
|
||||
|
||||
EnqueueRefreshPasteFormats();
|
||||
}
|
||||
@@ -275,6 +287,9 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(CustomAIUnavailableErrorText));
|
||||
OnPropertyChanged(nameof(IsCustomAIServiceEnabled));
|
||||
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
||||
OnPropertyChanged(nameof(CustomEndpoint));
|
||||
OnPropertyChanged(nameof(CustomModelName));
|
||||
OnPropertyChanged(nameof(DisableModeration));
|
||||
OnPropertyChanged(nameof(IsCustomAIAvailable));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
13
src/settings-ui/Settings.UI.Library/AdvancedPasteAIMode.cs
Normal file
13
src/settings-ui/Settings.UI.Library/AdvancedPasteAIMode.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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
|
||||
{
|
||||
public enum AdvancedPasteAIMode
|
||||
{
|
||||
Disabled = 0,
|
||||
OpenAI = 1,
|
||||
LocalModel = 2,
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
PasteAsJsonShortcut = new();
|
||||
CustomActions = new();
|
||||
AdditionalActions = new();
|
||||
AIMode = AdvancedPasteAIMode.Disabled;
|
||||
IsAdvancedAIEnabled = false;
|
||||
CustomEndpoint = string.Empty;
|
||||
CustomModelName = string.Empty;
|
||||
DisableModeration = false;
|
||||
ShowCustomPreview = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
}
|
||||
@@ -31,6 +35,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool IsAdvancedAIEnabled { get; set; }
|
||||
|
||||
public AdvancedPasteAIMode AIMode { get; set; }
|
||||
|
||||
public string CustomEndpoint { get; set; }
|
||||
|
||||
public string CustomModelName { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool DisableModeration { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowCustomPreview { get; set; }
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<InfoBar
|
||||
x:Uid="AdvancedPaste_LocalModelWarning"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsLocalModelMode, Mode=OneWay}"
|
||||
Severity="Warning"
|
||||
Visibility="{x:Bind ViewModel.IsLocalModelMode, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AdvancedPasteEnableAISettingsCard"
|
||||
x:Uid="AdvancedPaste_EnableAISettingsCard"
|
||||
@@ -72,17 +78,24 @@
|
||||
<tkcontrols:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="M128 766q0-42 24-77t65-48l178-57q32-11 61-30t52-42q50-50 71-114l58-179q13-40 48-65t78-26q42 0 77 24t50 65l58 177q21 66 72 117 49 50 117 72l176 58q43 14 69 48t26 80q0 41-25 76t-64 49l-178 58q-66 21-117 72-32 32-51 73t-33 84-26 83-30 73-45 51-71 20q-42 0-77-24t-49-65l-58-178q-8-25-19-47t-28-43q-34-43-77-68t-89-41-89-27-78-29-55-45-21-75zm1149 7q-76-29-145-53t-129-60-104-88-73-138l-57-176-67 176q-18 48-42 89t-60 78q-34 34-76 61t-89 43l-177 57q75 29 144 53t127 60 103 89 73 137l57 176 67-176q37-97 103-168t168-103l177-57zm-125 759q0-31 20-57t49-36l99-32q34-11 53-34t30-51 20-59 20-54 33-41 58-16q32 0 59 19t38 50q6 20 11 40t13 40 17 38 25 34q16 17 39 26t48 18 49 16 44 20 31 32 12 50q0 33-18 60t-51 38q-19 6-39 11t-41 13-39 17-34 25q-24 25-35 62t-24 73-35 61-68 25q-32 0-59-19t-38-50q-6-18-11-39t-13-41-17-40-24-33q-18-17-41-27t-47-17-49-15-43-20-30-33-12-54zm583 4q-43-13-74-30t-55-41-40-55-32-74q-12 41-29 72t-42 55-55 42-71 31q81 23 128 71t71 129q15-43 31-74t40-54 53-40 75-32z" />
|
||||
</tkcontrols:SettingsCard.HeaderIcon>
|
||||
<tkcontrols:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.IsOpenAIEnabled, Mode=OneWay}">
|
||||
<tkcontrols:Case Value="True">
|
||||
<Button x:Uid="AdvancedPaste_DisableAIButton" Click="AdvancedPaste_DisableAIButton_Click" />
|
||||
</tkcontrols:Case>
|
||||
<tkcontrols:Case Value="False">
|
||||
<Button
|
||||
x:Uid="AdvancedPaste_EnableAIButton"
|
||||
Click="AdvancedPaste_EnableAIButton_Click"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</tkcontrols:Case>
|
||||
</tkcontrols:SwitchPresenter>
|
||||
<ComboBox
|
||||
x:Name="AIModeComboBox"
|
||||
MinWidth="200"
|
||||
ItemsSource="{x:Bind ViewModel.AIModeOptions, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedAIMode, Mode=TwoWay}"
|
||||
SelectionChanged="AIModeComboBox_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock FontWeight="SemiBold" Text="{Binding DisplayName}" />
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Description}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Uid="AdvancedPaste_EnableAISettingsCardDescription" />
|
||||
@@ -94,9 +107,24 @@
|
||||
Name="AdvancedPasteEnableAdvancedAI"
|
||||
x:Uid="AdvancedPaste_EnableAdvancedAI"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/SemanticKernel.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsOpenAIEnabled, Mode=OneWay}">
|
||||
IsEnabled="{x:Bind ViewModel.IsAIEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsAdvancedAIEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsExpander
|
||||
x:Uid="AdvancedPaste_AdvancedSettings"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.ShowAdvancedSettings, Mode=OneWay}"
|
||||
IsExpanded="False"
|
||||
Visibility="{x:Bind ViewModel.ShowAdvancedSettings, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_AdvancedSettings_CustomEndpoint">
|
||||
<TextBox Text="{x:Bind ViewModel.CustomEndpoint, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_AdvancedSettings_CustomModelName">
|
||||
<TextBox Text="{x:Bind ViewModel.CustomModelName, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}">
|
||||
|
||||
@@ -48,26 +48,53 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async void AdvancedPaste_EnableAIButton_Click(object sender, RoutedEventArgs e)
|
||||
private async void AIModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
EnableAIDialog.PrimaryButtonText = resourceLoader.GetString("EnableAIDialog_SaveBtnText");
|
||||
EnableAIDialog.SecondaryButtonText = resourceLoader.GetString("EnableAIDialog_CancelBtnText");
|
||||
EnableAIDialog.PrimaryButtonCommand = SaveOpenAIKeyCommand;
|
||||
var comboBox = sender as ComboBox;
|
||||
var selectedItem = comboBox?.SelectedItem as AdvancedPasteViewModel.AIModeItem;
|
||||
|
||||
AdvancedPaste_EnableAIDialogOpenAIApiKey.Text = string.Empty;
|
||||
if (selectedItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ShowEnableDialogAsync();
|
||||
}
|
||||
// Prevent recursion when updating the ViewModel
|
||||
if (selectedItem.Mode == ViewModel.AIMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private async Task ShowEnableDialogAsync()
|
||||
{
|
||||
await EnableAIDialog.ShowAsync();
|
||||
}
|
||||
// If user selects OpenAI but doesn't have a key, show the setup dialog
|
||||
if (selectedItem.Mode == AdvancedPasteAIMode.OpenAI && !ViewModel.IsOpenAIEnabled)
|
||||
{
|
||||
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
EnableAIDialog.PrimaryButtonText = resourceLoader.GetString("EnableAIDialog_SaveBtnText");
|
||||
EnableAIDialog.SecondaryButtonText = resourceLoader.GetString("EnableAIDialog_CancelBtnText");
|
||||
EnableAIDialog.PrimaryButtonCommand = SaveOpenAIKeyCommand;
|
||||
|
||||
private void AdvancedPaste_DisableAIButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.DisableAI();
|
||||
AdvancedPaste_EnableAIDialogOpenAIApiKey.Text = string.Empty;
|
||||
|
||||
var result = await EnableAIDialog.ShowAsync();
|
||||
|
||||
// If user canceled the dialog, revert the selection
|
||||
if (result != ContentDialogResult.Primary || string.IsNullOrEmpty(AdvancedPaste_EnableAIDialogOpenAIApiKey.Text))
|
||||
{
|
||||
// Revert ComboBox selection without triggering change event
|
||||
comboBox.SelectionChanged -= AIModeComboBox_SelectionChanged;
|
||||
comboBox.SelectedItem = ViewModel.SelectedAIMode;
|
||||
comboBox.SelectionChanged += AIModeComboBox_SelectionChanged;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If user selects Disabled and had OpenAI enabled, disable it
|
||||
if (selectedItem.Mode == AdvancedPasteAIMode.Disabled && ViewModel.IsOpenAIEnabled)
|
||||
{
|
||||
ViewModel.DisableAI();
|
||||
}
|
||||
|
||||
// Update the ViewModel (this will handle the actual mode change)
|
||||
ViewModel.AIMode = selectedItem.Mode;
|
||||
}
|
||||
|
||||
private void AdvancedPaste_EnableAIDialogOpenAIApiKey_TextChanged(object sender, TextChangedEventArgs e)
|
||||
|
||||
@@ -638,6 +638,30 @@ opera.exe</value>
|
||||
<data name="AdvancedPaste_Additional_Actions_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Additional actions</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings.Header" xml:space="preserve">
|
||||
<value>Advanced settings</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings.Description" xml:space="preserve">
|
||||
<value>These settings allow you to connect to self-hosted models or alternative providers, which are compatible with the OpenAI API specification.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_CustomEndpoint.Header" xml:space="preserve">
|
||||
<value>Custom endpoint</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_CustomEndpoint.Description" xml:space="preserve">
|
||||
<value>Enter the base URL of the OpenAI API-compatible service you wish to use (e.g., https://generativelanguage.googleapis.com/v1beta or https://api.anthropic.com/v1). This will override the default endpoint.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_CustomModelName.Header" xml:space="preserve">
|
||||
<value>Custom model name</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_CustomModelName.Description" xml:space="preserve">
|
||||
<value>Specify the exact model identifier that your custom endpoint expects (e.g., claude-3-7-sonnet, gemini-2.5-flash, my-custom-finetune). Consult your provider's documentation for the correct name.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_DisableModeration.Header" xml:space="preserve">
|
||||
<value>Disable moderation</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_AdvancedSettings_DisableModeration.Description" xml:space="preserve">
|
||||
<value>Disable the built-in moderation checks for the selected model. This is not recommended unless you are using a custom model that has its own moderation capabilities.</value>
|
||||
</data>
|
||||
<data name="RemapKeysList.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Current key remappings</value>
|
||||
</data>
|
||||
@@ -3931,6 +3955,12 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Description" xml:space="preserve">
|
||||
<value>Add advanced capabilities when using 'Paste with AI' including the power to 'chain' multiple transformations together and work with images and files. This feature may consume more API credits when used.</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_LocalModelWarning.Title" xml:space="preserve">
|
||||
<value>Local Model Warning</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_LocalModelWarning.Message" xml:space="preserve">
|
||||
<value>When using a local model, PowerToys will send your clipboard data to the endpoint you specify. Ensure you trust the endpoint and understand the privacy implications. Moderation is automatically disabled for local models.</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>
|
||||
</data>
|
||||
|
||||
@@ -361,6 +361,113 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public AdvancedPasteAIMode AIMode
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.AIMode;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.AIMode)
|
||||
{
|
||||
_advancedPasteSettings.Properties.AIMode = value;
|
||||
|
||||
// Auto-disable moderation for local models
|
||||
if (value == AdvancedPasteAIMode.LocalModel)
|
||||
{
|
||||
_advancedPasteSettings.Properties.DisableModeration = true;
|
||||
}
|
||||
else if (value == AdvancedPasteAIMode.OpenAI)
|
||||
{
|
||||
_advancedPasteSettings.Properties.DisableModeration = false;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(AIMode));
|
||||
OnPropertyChanged(nameof(IsAIEnabled));
|
||||
OnPropertyChanged(nameof(IsLocalModelMode));
|
||||
OnPropertyChanged(nameof(ShowAdvancedSettings));
|
||||
OnPropertyChanged(nameof(DisableModeration));
|
||||
OnPropertyChanged(nameof(SelectedAIMode));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAIEnabled => AIMode != AdvancedPasteAIMode.Disabled && IsOpenAIEnabled;
|
||||
|
||||
public bool IsLocalModelMode => AIMode == AdvancedPasteAIMode.LocalModel;
|
||||
|
||||
public bool ShowAdvancedSettings => IsLocalModelMode;
|
||||
|
||||
public class AIModeItem
|
||||
{
|
||||
public AdvancedPasteAIMode Mode { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
}
|
||||
|
||||
public AIModeItem[] AIModeOptions { get; } = new[]
|
||||
{
|
||||
new AIModeItem { Mode = AdvancedPasteAIMode.Disabled, DisplayName = "Disabled", Description = "AI features are disabled" },
|
||||
new AIModeItem { Mode = AdvancedPasteAIMode.OpenAI, DisplayName = "OpenAI", Description = "Use OpenAI cloud service" },
|
||||
new AIModeItem { Mode = AdvancedPasteAIMode.LocalModel, DisplayName = "Local model", Description = "Use a local AI model endpoint" },
|
||||
};
|
||||
|
||||
public AIModeItem SelectedAIMode
|
||||
{
|
||||
get => AIModeOptions.FirstOrDefault(x => x.Mode == AIMode) ?? AIModeOptions[0];
|
||||
set
|
||||
{
|
||||
if (value != null && value.Mode != AIMode)
|
||||
{
|
||||
AIMode = value.Mode;
|
||||
OnPropertyChanged(nameof(SelectedAIMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CustomEndpoint
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.CustomEndpoint;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.CustomEndpoint)
|
||||
{
|
||||
_advancedPasteSettings.Properties.CustomEndpoint = value;
|
||||
OnPropertyChanged(nameof(CustomEndpoint));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CustomModelName
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.CustomModelName;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.CustomModelName)
|
||||
{
|
||||
_advancedPasteSettings.Properties.CustomModelName = value;
|
||||
OnPropertyChanged(nameof(CustomModelName));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DisableModeration
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.DisableModeration;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.DisableModeration)
|
||||
{
|
||||
_advancedPasteSettings.Properties.DisableModeration = value;
|
||||
OnPropertyChanged(nameof(DisableModeration));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowCustomPreview
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.ShowCustomPreview;
|
||||
@@ -460,7 +567,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
PasswordVault vault = new PasswordVault();
|
||||
PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
vault.Remove(cred);
|
||||
AIMode = AdvancedPasteAIMode.Disabled;
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
OnPropertyChanged(nameof(SelectedAIMode));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -475,7 +584,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
PasswordVault vault = new();
|
||||
PasswordCredential cred = new("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey", password);
|
||||
vault.Add(cred);
|
||||
AIMode = AdvancedPasteAIMode.OpenAI; // Default to OpenAI when enabling
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
OnPropertyChanged(nameof(SelectedAIMode));
|
||||
IsAdvancedAIEnabled = true; // new users should get Semantic Kernel benefits immediately
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user