2024-05-09 10:32:03 -04:00
// 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 ;
2024-08-22 16:17:12 +02:00
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Collections.Specialized ;
using System.ComponentModel ;
2024-05-09 10:32:03 -04:00
using System.Globalization ;
2025-11-05 16:13:55 +08:00
using System.IO.Abstractions ;
2024-08-22 16:17:12 +02:00
using System.Linq ;
2025-11-05 16:13:55 +08:00
using System.Runtime.Versioning ;
2024-05-09 10:32:03 -04:00
using System.Text.Json ;
2025-11-05 16:13:55 +08:00
2024-05-09 10:32:03 -04:00
using global : : PowerToys . GPOWrapper ;
2025-08-20 09:31:52 +08:00
using Microsoft.PowerToys.Settings.UI.Helpers ;
2024-05-09 10:32:03 -04:00
using Microsoft.PowerToys.Settings.UI.Library ;
using Microsoft.PowerToys.Settings.UI.Library.Helpers ;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces ;
2025-11-05 16:13:55 +08:00
using Microsoft.PowerToys.Settings.UI.Library.Utilities ;
2025-02-25 02:48:54 +08:00
using Microsoft.PowerToys.Settings.UI.SerializationContext ;
2025-11-05 16:13:55 +08:00
using Microsoft.UI.Dispatching ;
2024-05-09 10:32:03 -04:00
using Microsoft.Win32 ;
using Windows.Security.Credentials ;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
2025-08-20 09:31:52 +08:00
public partial class AdvancedPasteViewModel : PageViewModelBase
2024-05-09 10:32:03 -04:00
{
2024-08-22 16:17:12 +02:00
private static readonly HashSet < string > WarnHotkeys = [ "Ctrl + V" , "Ctrl + Shift + V" ] ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
private bool _disposed ;
private PasteAIProviderDefinition _pasteAIProviderDraft ;
private PasteAIProviderDefinition _editingPasteAIProvider ;
2024-05-09 10:32:03 -04:00
2025-08-20 09:31:52 +08:00
protected override string ModuleName = > AdvancedPasteSettings . ModuleName ;
2024-05-09 10:32:03 -04:00
private GeneralSettings GeneralSettingsConfig { get ; set ; }
2025-12-19 03:30:01 +01:00
private readonly SettingsUtils _settingsUtils ;
2024-05-09 10:32:03 -04:00
private readonly AdvancedPasteSettings _advancedPasteSettings ;
2024-10-18 15:34:09 +02:00
private readonly AdvancedPasteAdditionalActions _additionalActions ;
2024-08-22 16:17:12 +02:00
private readonly ObservableCollection < AdvancedPasteCustomAction > _customActions ;
2025-11-05 16:13:55 +08:00
private readonly DispatcherQueue _dispatcherQueue ;
private IFileSystemWatcher _settingsWatcher ;
private bool _suppressSave ;
2024-05-09 10:32:03 -04:00
private GpoRuleConfigured _enabledGpoRuleConfiguration ;
private bool _enabledStateIsGPOConfigured ;
2024-05-26 13:22:50 +02:00
private GpoRuleConfigured _onlineAIModelsGpoRuleConfiguration ;
private bool _onlineAIModelsDisallowedByGPO ;
2024-05-09 10:32:03 -04:00
private bool _isEnabled ;
private Func < string , int > SendConfigMSG { get ; }
2025-11-11 20:07:41 +08:00
private static readonly HashSet < string > CustomActionNonPersistedProperties = new ( StringComparer . Ordinal )
{
nameof ( AdvancedPasteCustomAction . CanMoveUp ) ,
nameof ( AdvancedPasteCustomAction . CanMoveDown ) ,
nameof ( AdvancedPasteCustomAction . IsValid ) ,
nameof ( AdvancedPasteCustomAction . HasConflict ) ,
nameof ( AdvancedPasteCustomAction . Tooltip ) ,
nameof ( AdvancedPasteCustomAction . SubActions ) ,
} ;
2024-05-09 10:32:03 -04:00
public AdvancedPasteViewModel (
2025-12-19 03:30:01 +01:00
SettingsUtils settingsUtils ,
2024-05-09 10:32:03 -04:00
ISettingsRepository < GeneralSettings > settingsRepository ,
ISettingsRepository < AdvancedPasteSettings > advancedPasteSettingsRepository ,
Func < string , int > ipcMSGCallBackFunc )
{
// To obtain the general settings configurations of PowerToys Settings.
ArgumentNullException . ThrowIfNull ( settingsRepository ) ;
GeneralSettingsConfig = settingsRepository . SettingsConfig ;
2026-01-26 15:28:59 +08:00
// To obtain the settings configurations of Advanced Paste.
ArgumentNullException . ThrowIfNull ( advancedPasteSettingsRepository ) ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
_dispatcherQueue = DispatcherQueue . GetForCurrentThread ( ) ;
2024-05-09 10:32:03 -04:00
_settingsUtils = settingsUtils ? ? throw new ArgumentNullException ( nameof ( settingsUtils ) ) ;
2026-01-26 15:28:59 +08:00
_advancedPasteSettings = advancedPasteSettingsRepository . SettingsConfig ? ? throw new ArgumentException ( "SettingsConfig cannot be null" , nameof ( advancedPasteSettingsRepository ) ) ;
2024-05-09 10:32:03 -04:00
2026-01-26 15:28:59 +08:00
if ( _advancedPasteSettings . Properties is null )
{
throw new ArgumentException ( "AdvancedPasteSettings.Properties cannot be null" , nameof ( advancedPasteSettingsRepository ) ) ;
}
// Ensure AdditionalActions and CustomActions are initialized to prevent null reference exceptions
// This handles legacy settings files that may be missing these properties
_advancedPasteSettings . Properties . AdditionalActions ? ? = new AdvancedPasteAdditionalActions ( ) ;
_advancedPasteSettings . Properties . CustomActions ? ? = new AdvancedPasteCustomActions ( ) ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
AttachConfigurationHandlers ( ) ;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc ;
2024-10-18 15:34:09 +02:00
_additionalActions = _advancedPasteSettings . Properties . AdditionalActions ;
2026-01-26 15:28:59 +08:00
_customActions = _advancedPasteSettings . Properties . CustomActions . Value ? ? new ObservableCollection < AdvancedPasteCustomAction > ( ) ;
2024-08-22 16:17:12 +02:00
2025-11-05 16:13:55 +08:00
SetupSettingsFileWatcher ( ) ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
InitializePasteAIProviderState ( ) ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
InitializeEnabledValue ( ) ;
MigrateLegacyAIEnablement ( ) ;
2024-08-22 16:17:12 +02:00
2025-02-25 22:33:39 +01:00
foreach ( var action in _additionalActions . GetAllActions ( ) )
2024-10-18 15:34:09 +02:00
{
action . PropertyChanged + = OnAdditionalActionPropertyChanged ;
}
2024-08-22 16:17:12 +02:00
foreach ( var customAction in _customActions )
{
customAction . PropertyChanged + = OnCustomActionPropertyChanged ;
}
_customActions . CollectionChanged + = OnCustomActionsCollectionChanged ;
UpdateCustomActionsCanMoveUpDown ( ) ;
2024-05-09 10:32:03 -04:00
}
2025-08-20 09:31:52 +08:00
public override Dictionary < string , HotkeySettings [ ] > GetAllHotkeySettings ( )
{
var hotkeySettings = new List < HotkeySettings >
{
PasteAsPlainTextShortcut ,
AdvancedPasteUIShortcut ,
PasteAsMarkdownShortcut ,
PasteAsJsonShortcut ,
} ;
foreach ( var action in _additionalActions . GetAllActions ( ) )
{
if ( action is AdvancedPasteAdditionalAction additionalAction )
{
hotkeySettings . Add ( additionalAction . Shortcut ) ;
}
}
// Custom actions do not have localization header, just use the action name.
foreach ( var customAction in _customActions )
{
hotkeySettings . Add ( customAction . Shortcut ) ;
}
return new Dictionary < string , HotkeySettings [ ] >
{
[ModuleName] = hotkeySettings . ToArray ( ) ,
} ;
}
2024-05-09 10:32:03 -04:00
private void InitializeEnabledValue ( )
{
_enabledGpoRuleConfiguration = GPOWrapper . GetConfiguredAdvancedPasteEnabledValue ( ) ;
if ( _enabledGpoRuleConfiguration = = GpoRuleConfigured . Disabled | | _enabledGpoRuleConfiguration = = GpoRuleConfigured . Enabled )
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true ;
_isEnabled = _enabledGpoRuleConfiguration = = GpoRuleConfigured . Enabled ;
}
else
{
_isEnabled = GeneralSettingsConfig . Enabled . AdvancedPaste ;
}
2024-05-26 13:22:50 +02:00
_onlineAIModelsGpoRuleConfiguration = GPOWrapper . GetAllowedAdvancedPasteOnlineAIModelsValue ( ) ;
2025-11-05 16:13:55 +08:00
_onlineAIModelsDisallowedByGPO = _onlineAIModelsGpoRuleConfiguration = = GpoRuleConfigured . Disabled ;
2024-05-26 13:22:50 +02:00
2025-11-05 16:13:55 +08:00
if ( _onlineAIModelsDisallowedByGPO )
{
2024-05-26 13:22:50 +02:00
// disable AI if it was enabled
DisableAI ( ) ;
}
2024-05-09 10:32:03 -04:00
}
2025-11-05 16:13:55 +08:00
private void MigrateLegacyAIEnablement ( )
{
2025-11-12 16:24:21 +08:00
var properties = _advancedPasteSettings ? . Properties ;
if ( properties is null )
2025-11-05 16:13:55 +08:00
{
return ;
}
2025-11-12 16:24:21 +08:00
bool legacyAdvancedAIConsumed = properties . TryConsumeLegacyAdvancedAIEnabled ( out var advancedFlag ) ;
bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed & & advancedFlag ;
if ( IsOnlineAIModelsDisallowedByGPO )
2025-11-05 16:13:55 +08:00
{
2025-11-12 16:24:21 +08:00
if ( legacyAdvancedAIConsumed )
{
SaveAndNotifySettings ( ) ;
}
2025-11-05 16:13:55 +08:00
return ;
}
2025-11-12 16:24:21 +08:00
PasswordCredential legacyCredential = TryGetLegacyOpenAICredential ( ) ;
2025-11-14 16:56:23 +08:00
if ( legacyCredential is null )
2025-11-12 16:24:21 +08:00
{
2025-11-14 16:56:23 +08:00
if ( legacyAdvancedAIConsumed )
{
SaveAndNotifySettings ( ) ;
}
2025-11-12 16:24:21 +08:00
return ;
}
2025-11-14 16:56:23 +08:00
var configuration = properties . PasteAIConfiguration ;
if ( configuration is null )
2025-11-12 16:24:21 +08:00
{
2025-11-14 16:56:23 +08:00
configuration = new PasteAIConfiguration ( ) ;
properties . PasteAIConfiguration = configuration ;
2025-11-12 16:24:21 +08:00
}
2025-11-14 16:56:23 +08:00
bool configurationUpdated = false ;
var ensureResult = AdvancedPasteMigrationHelper . EnsureOpenAIProvider ( configuration ) ;
PasteAIProviderDefinition openAIProvider = ensureResult . Provider ;
configurationUpdated | = ensureResult . Updated ;
2025-11-12 16:24:21 +08:00
if ( legacyAdvancedAIConsumed & & openAIProvider is not null & & openAIProvider . EnableAdvancedAI ! = legacyAdvancedAIEnabled )
{
openAIProvider . EnableAdvancedAI = legacyAdvancedAIEnabled ;
configurationUpdated = true ;
}
if ( legacyCredential is not null & & openAIProvider is not null )
{
SavePasteAIApiKey ( openAIProvider . Id , openAIProvider . ServiceType , legacyCredential . Password ) ;
RemoveLegacyOpenAICredential ( ) ;
}
2025-11-14 16:56:23 +08:00
const bool shouldEnableAI = true ;
2025-11-12 16:24:21 +08:00
bool enabledChanged = false ;
2025-11-13 16:55:09 +08:00
if ( properties . IsAIEnabled ! = shouldEnableAI )
2025-11-12 16:24:21 +08:00
{
2025-11-13 16:55:09 +08:00
properties . IsAIEnabled = shouldEnableAI ;
2025-11-12 16:24:21 +08:00
enabledChanged = true ;
}
bool shouldPersist = configurationUpdated | | enabledChanged | | legacyAdvancedAIConsumed ;
if ( shouldPersist )
{
SaveAndNotifySettings ( ) ;
if ( configurationUpdated )
{
OnPropertyChanged ( nameof ( PasteAIConfiguration ) ) ;
}
if ( enabledChanged )
{
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
}
}
2025-11-05 16:13:55 +08:00
}
2024-05-09 10:32:03 -04:00
public bool IsEnabled
{
get = > _isEnabled ;
set
{
if ( _enabledStateIsGPOConfigured )
{
// If it's GPO configured, shouldn't be able to change this state.
return ;
}
if ( _isEnabled ! = value )
{
_isEnabled = value ;
OnPropertyChanged ( nameof ( IsEnabled ) ) ;
2024-06-08 22:56:01 +02:00
OnPropertyChanged ( nameof ( ShowOnlineAIModelsGpoConfiguredInfoBar ) ) ;
OnPropertyChanged ( nameof ( ShowClipboardHistoryIsGpoConfiguredInfoBar ) ) ;
2024-05-09 10:32:03 -04:00
// Set the status of AdvancedPaste in the general settings
GeneralSettingsConfig . Enabled . AdvancedPaste = value ;
var outgoing = new OutGoingGeneralSettings ( GeneralSettingsConfig ) ;
SendConfigMSG ( outgoing . ToString ( ) ) ;
}
}
}
2024-08-22 16:17:12 +02:00
public ObservableCollection < AdvancedPasteCustomAction > CustomActions = > _customActions ;
2024-10-18 15:34:09 +02:00
public AdvancedPasteAdditionalActions AdditionalActions = > _additionalActions ;
2025-11-05 16:13:55 +08:00
public static IEnumerable < AIServiceTypeMetadata > AvailableProviders = > AIServiceTypeRegistry . GetAvailableServiceTypes ( ) ;
2024-05-09 10:32:03 -04:00
2025-11-05 16:13:55 +08:00
/// <summary>
/// Gets available AI providers filtered by GPO policies.
/// Only returns providers that are not explicitly disabled by GPO.
/// </summary>
public IEnumerable < AIServiceTypeMetadata > AvailableProvidersFilteredByGPO = >
AvailableProviders . Where ( metadata = > IsServiceTypeAllowedByGPO ( metadata . ServiceType ) ) ;
public bool IsAIEnabled = > _advancedPasteSettings . Properties . IsAIEnabled & & ! IsOnlineAIModelsDisallowedByGPO ;
2025-11-12 16:24:21 +08:00
private PasswordCredential TryGetLegacyOpenAICredential ( )
2025-11-05 16:13:55 +08:00
{
2024-05-09 10:32:03 -04:00
try
{
2025-11-05 16:13:55 +08:00
PasswordVault vault = new ( ) ;
2025-11-12 16:24:21 +08:00
var credential = vault . Retrieve ( "https://platform.openai.com/api-keys" , "PowerToys_AdvancedPaste_OpenAIKey" ) ;
credential ? . RetrievePassword ( ) ;
return credential ;
}
catch ( Exception )
{
return null ;
}
}
2025-11-05 16:13:55 +08:00
2025-11-12 16:24:21 +08:00
private void RemoveLegacyOpenAICredential ( )
{
try
{
PasswordVault vault = new ( ) ;
TryRemoveCredential ( vault , "https://platform.openai.com/api-keys" , "PowerToys_AdvancedPaste_OpenAIKey" ) ;
2024-05-09 10:32:03 -04:00
}
catch ( Exception )
{
}
}
public bool IsEnabledGpoConfigured
{
get = > _enabledStateIsGPOConfigured ;
}
2024-05-26 13:22:50 +02:00
public bool IsOnlineAIModelsDisallowedByGPO
{
get = > _onlineAIModelsDisallowedByGPO | | _enabledGpoRuleConfiguration = = GpoRuleConfigured . Disabled ;
}
public bool ShowOnlineAIModelsGpoConfiguredInfoBar
{
2024-06-08 22:56:01 +02:00
get = > _onlineAIModelsDisallowedByGPO & & _isEnabled ;
2024-05-26 13:22:50 +02:00
}
2024-05-09 10:32:03 -04:00
private bool IsClipboardHistoryEnabled ( )
{
string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\" ;
try
{
int enableClipboardHistory = ( int ) Registry . GetValue ( registryKey , "EnableClipboardHistory" , false ) ;
return enableClipboardHistory ! = 0 ;
}
catch ( Exception )
{
return false ;
}
}
2024-05-24 00:19:01 +01:00
private bool IsClipboardHistoryDisabledByGPO ( )
{
string registryKey = @"HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\System\" ;
try
{
object allowClipboardHistory = Registry . GetValue ( registryKey , "AllowClipboardHistory" , null ) ;
if ( allowClipboardHistory ! = null )
{
return ( int ) allowClipboardHistory = = 0 ;
}
else
{
return false ;
}
}
catch ( Exception )
{
return false ;
}
}
2024-05-09 10:32:03 -04:00
private void SetClipboardHistoryEnabled ( bool value )
{
string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\" ;
try
{
Registry . SetValue ( registryKey , "EnableClipboardHistory" , value ? 1 : 0 ) ;
}
catch ( Exception )
{
}
}
public bool ClipboardHistoryEnabled
{
get = > IsClipboardHistoryEnabled ( ) ;
set
{
if ( IsClipboardHistoryEnabled ( ) ! = value )
{
SetClipboardHistoryEnabled ( value ) ;
}
}
}
2024-05-24 00:19:01 +01:00
public bool ClipboardHistoryDisabledByGPO
{
get = > IsClipboardHistoryDisabledByGPO ( ) ;
}
2024-06-08 22:56:01 +02:00
public bool ShowClipboardHistoryIsGpoConfiguredInfoBar
{
get = > IsClipboardHistoryDisabledByGPO ( ) & & _isEnabled ;
}
2024-05-09 10:32:03 -04:00
public HotkeySettings AdvancedPasteUIShortcut
{
get = > _advancedPasteSettings . Properties . AdvancedPasteUIShortcut ;
set
{
if ( _advancedPasteSettings . Properties . AdvancedPasteUIShortcut ! = value )
{
_advancedPasteSettings . Properties . AdvancedPasteUIShortcut = value ? ? AdvancedPasteProperties . DefaultAdvancedPasteUIShortcut ;
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
2025-08-20 09:31:52 +08:00
OnPropertyChanged ( nameof ( AdvancedPasteUIShortcut ) ) ;
2024-08-22 16:17:12 +02:00
SaveAndNotifySettings ( ) ;
2024-05-09 10:32:03 -04:00
}
}
}
public HotkeySettings PasteAsPlainTextShortcut
{
get = > _advancedPasteSettings . Properties . PasteAsPlainTextShortcut ;
set
{
if ( _advancedPasteSettings . Properties . PasteAsPlainTextShortcut ! = value )
{
_advancedPasteSettings . Properties . PasteAsPlainTextShortcut = value ? ? AdvancedPasteProperties . DefaultPasteAsPlainTextShortcut ;
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
2025-08-20 09:31:52 +08:00
OnPropertyChanged ( nameof ( PasteAsPlainTextShortcut ) ) ;
2024-08-22 16:17:12 +02:00
SaveAndNotifySettings ( ) ;
2024-05-09 10:32:03 -04:00
}
}
}
public HotkeySettings PasteAsMarkdownShortcut
{
get = > _advancedPasteSettings . Properties . PasteAsMarkdownShortcut ;
set
{
if ( _advancedPasteSettings . Properties . PasteAsMarkdownShortcut ! = value )
{
_advancedPasteSettings . Properties . PasteAsMarkdownShortcut = value ? ? new HotkeySettings ( ) ;
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
2025-08-20 09:31:52 +08:00
OnPropertyChanged ( nameof ( PasteAsMarkdownShortcut ) ) ;
2024-08-22 16:17:12 +02:00
SaveAndNotifySettings ( ) ;
2024-05-09 10:32:03 -04:00
}
}
}
public HotkeySettings PasteAsJsonShortcut
{
get = > _advancedPasteSettings . Properties . PasteAsJsonShortcut ;
set
{
if ( _advancedPasteSettings . Properties . PasteAsJsonShortcut ! = value )
{
_advancedPasteSettings . Properties . PasteAsJsonShortcut = value ? ? new HotkeySettings ( ) ;
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
2025-08-20 09:31:52 +08:00
OnPropertyChanged ( nameof ( PasteAsJsonShortcut ) ) ;
2024-08-22 16:17:12 +02:00
SaveAndNotifySettings ( ) ;
2024-05-09 10:32:03 -04:00
}
}
}
2025-11-05 16:13:55 +08:00
public PasteAIConfiguration PasteAIConfiguration
2024-12-11 10:28:44 +01:00
{
2026-01-26 15:28:59 +08:00
get
{
// Ensure PasteAIConfiguration is never null for XAML binding
_advancedPasteSettings . Properties . PasteAIConfiguration ? ? = new PasteAIConfiguration ( ) ;
return _advancedPasteSettings . Properties . PasteAIConfiguration ;
}
2024-12-11 10:28:44 +01:00
set
{
2025-11-05 16:13:55 +08:00
if ( ! ReferenceEquals ( value , _advancedPasteSettings . Properties . PasteAIConfiguration ) )
2024-12-11 10:28:44 +01:00
{
2025-11-05 16:13:55 +08:00
UnsubscribeFromPasteAIConfiguration ( _advancedPasteSettings . Properties . PasteAIConfiguration ) ;
var newValue = value ? ? new PasteAIConfiguration ( ) ;
_advancedPasteSettings . Properties . PasteAIConfiguration = newValue ;
SubscribeToPasteAIConfiguration ( newValue ) ;
OnPropertyChanged ( nameof ( PasteAIConfiguration ) ) ;
SaveAndNotifySettings ( ) ;
}
}
}
public PasteAIProviderDefinition PasteAIProviderDraft
{
get = > _pasteAIProviderDraft ;
private set
{
if ( ! ReferenceEquals ( _pasteAIProviderDraft , value ) )
{
_pasteAIProviderDraft = value ;
OnPropertyChanged ( nameof ( PasteAIProviderDraft ) ) ;
OnPropertyChanged ( nameof ( ShowPasteAIProviderGpoConfiguredInfoBar ) ) ;
}
}
}
public bool ShowPasteAIProviderGpoConfiguredInfoBar
{
get
{
if ( _pasteAIProviderDraft is null )
{
return false ;
2024-12-11 10:28:44 +01:00
}
2025-11-05 16:13:55 +08:00
var serviceType = _pasteAIProviderDraft . ServiceType . ToAIServiceType ( ) ;
return ! IsServiceTypeAllowedByGPO ( serviceType ) ;
2024-12-11 10:28:44 +01:00
}
}
2025-11-05 16:13:55 +08:00
public bool IsEditingPasteAIProvider = > _editingPasteAIProvider is not null ;
2024-05-09 10:32:03 -04:00
public bool ShowCustomPreview
{
get = > _advancedPasteSettings . Properties . ShowCustomPreview ;
set
{
if ( value ! = _advancedPasteSettings . Properties . ShowCustomPreview )
{
_advancedPasteSettings . Properties . ShowCustomPreview = value ;
NotifySettingsChanged ( ) ;
}
}
}
2024-06-24 16:03:46 +02:00
public bool CloseAfterLosingFocus
{
get = > _advancedPasteSettings . Properties . CloseAfterLosingFocus ;
set
{
if ( value ! = _advancedPasteSettings . Properties . CloseAfterLosingFocus )
{
_advancedPasteSettings . Properties . CloseAfterLosingFocus = value ;
NotifySettingsChanged ( ) ;
}
}
}
2025-11-13 10:18:13 +08:00
public bool EnableClipboardPreview
{
get = > _advancedPasteSettings . Properties . EnableClipboardPreview ;
set
{
if ( value ! = _advancedPasteSettings . Properties . EnableClipboardPreview )
{
_advancedPasteSettings . Properties . EnableClipboardPreview = value ;
NotifySettingsChanged ( ) ;
}
}
}
2024-10-18 15:34:09 +02:00
public bool IsConflictingCopyShortcut = >
_customActions . Select ( customAction = > customAction . Shortcut )
. Concat ( [ PasteAsPlainTextShortcut , AdvancedPasteUIShortcut , PasteAsMarkdownShortcut , PasteAsJsonShortcut ] )
. Any ( hotkey = > WarnHotkeys . Contains ( hotkey . ToString ( ) ) ) ;
public bool IsAdditionalActionConflictingCopyShortcut = >
2025-02-25 22:33:39 +01:00
_additionalActions . GetAllActions ( )
2024-10-18 15:34:09 +02:00
. OfType < AdvancedPasteAdditionalAction > ( )
. Select ( additionalAction = > additionalAction . Shortcut )
. Any ( hotkey = > WarnHotkeys . Contains ( hotkey . ToString ( ) ) ) ;
2024-05-09 10:32:03 -04:00
private void NotifySettingsChanged ( )
{
// Using InvariantCulture as this is an IPC message
SendConfigMSG (
2024-06-24 16:03:46 +02:00
string . Format (
CultureInfo . InvariantCulture ,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}" ,
AdvancedPasteSettings . ModuleName ,
2025-02-25 02:48:54 +08:00
JsonSerializer . Serialize ( _advancedPasteSettings , SourceGenerationContextContext . Default . AdvancedPasteSettings ) ) ) ;
2024-05-09 10:32:03 -04:00
}
public void RefreshEnabledState ( )
{
InitializeEnabledValue ( ) ;
2025-11-05 16:13:55 +08:00
MigrateLegacyAIEnablement ( ) ;
2024-05-09 10:32:03 -04:00
OnPropertyChanged ( nameof ( IsEnabled ) ) ;
2024-06-08 22:56:01 +02:00
OnPropertyChanged ( nameof ( ShowOnlineAIModelsGpoConfiguredInfoBar ) ) ;
OnPropertyChanged ( nameof ( ShowClipboardHistoryIsGpoConfiguredInfoBar ) ) ;
2025-11-05 16:13:55 +08:00
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
}
public void BeginAddPasteAIProvider ( string serviceType )
{
var normalizedServiceType = NormalizeServiceType ( serviceType , out var persistedServiceType ) ;
var metadata = AIServiceTypeRegistry . GetMetadata ( normalizedServiceType ) ;
var provider = new PasteAIProviderDefinition
{
ServiceType = persistedServiceType ,
2025-11-12 16:24:21 +08:00
ModelName = PasteAIProviderDefaults . GetDefaultModelName ( normalizedServiceType ) ,
2025-11-05 16:13:55 +08:00
EndpointUrl = string . Empty ,
ApiVersion = string . Empty ,
DeploymentName = string . Empty ,
ModelPath = string . Empty ,
SystemPrompt = string . Empty ,
ModerationEnabled = normalizedServiceType = = AIServiceType . OpenAI ,
IsLocalModel = metadata . IsLocalModel ,
} ;
if ( normalizedServiceType is AIServiceType . FoundryLocal or AIServiceType . Onnx or AIServiceType . ML )
{
provider . ModelName = string . Empty ;
}
_editingPasteAIProvider = null ;
PasteAIProviderDraft = provider ;
}
private static AIServiceType NormalizeServiceType ( string serviceType , out string persistedServiceType )
{
if ( string . IsNullOrWhiteSpace ( serviceType ) )
{
persistedServiceType = AIServiceType . OpenAI . ToConfigurationString ( ) ;
return AIServiceType . OpenAI ;
}
var trimmed = serviceType . Trim ( ) ;
var serviceTypeKind = trimmed . ToAIServiceType ( ) ;
if ( serviceTypeKind = = AIServiceType . Unknown )
{
persistedServiceType = AIServiceType . OpenAI . ToConfigurationString ( ) ;
return AIServiceType . OpenAI ;
}
persistedServiceType = trimmed ;
return serviceTypeKind ;
}
public bool IsServiceTypeAllowedByGPO ( AIServiceType serviceType )
{
var metadata = AIServiceTypeRegistry . GetMetadata ( serviceType ) ;
// Check if this is an online service
if ( metadata . IsOnlineService )
{
// For online services, first check the global online AI models GPO
if ( _onlineAIModelsGpoRuleConfiguration = = GpoRuleConfigured . Disabled )
{
// If global online AI is disabled, all online services are blocked
return false ;
}
// If global online AI is enabled or not configured, check individual endpoint GPO
var individualGpoRule = serviceType switch
{
AIServiceType . OpenAI = > GPOWrapper . GetAllowedAdvancedPasteOpenAIValue ( ) ,
AIServiceType . AzureOpenAI = > GPOWrapper . GetAllowedAdvancedPasteAzureOpenAIValue ( ) ,
AIServiceType . AzureAIInference = > GPOWrapper . GetAllowedAdvancedPasteAzureAIInferenceValue ( ) ,
AIServiceType . Mistral = > GPOWrapper . GetAllowedAdvancedPasteMistralValue ( ) ,
AIServiceType . Google = > GPOWrapper . GetAllowedAdvancedPasteGoogleValue ( ) ,
_ = > GpoRuleConfigured . Unavailable ,
} ;
// If individual GPO is explicitly disabled, block it
return individualGpoRule ! = GpoRuleConfigured . Disabled ;
}
else
{
// For local models, only check their individual GPO (not affected by online AI GPO)
var localGpoRule = serviceType switch
{
AIServiceType . Ollama = > GPOWrapper . GetAllowedAdvancedPasteOllamaValue ( ) ,
AIServiceType . FoundryLocal = > GPOWrapper . GetAllowedAdvancedPasteFoundryLocalValue ( ) ,
_ = > GpoRuleConfigured . Unavailable ,
} ;
// If local model GPO is explicitly disabled, block it
return localGpoRule ! = GpoRuleConfigured . Disabled ;
}
}
public void BeginEditPasteAIProvider ( PasteAIProviderDefinition provider )
{
ArgumentNullException . ThrowIfNull ( provider ) ;
_editingPasteAIProvider = provider ;
var draft = provider . Clone ( ) ;
var storedEndpoint = GetPasteAIEndpoint ( draft . Id , draft . ServiceType ) ;
if ( ! string . IsNullOrWhiteSpace ( storedEndpoint ) )
{
draft . EndpointUrl = storedEndpoint ;
}
PasteAIProviderDraft = draft ;
}
public void CancelPasteAIProviderDraft ( )
{
PasteAIProviderDraft = null ;
_editingPasteAIProvider = null ;
}
public void CommitPasteAIProviderDraft ( string apiKey , string endpoint )
{
if ( PasteAIProviderDraft is null )
{
return ;
}
var config = PasteAIConfiguration ? ? new PasteAIConfiguration ( ) ;
if ( _advancedPasteSettings . Properties . PasteAIConfiguration is null )
{
PasteAIConfiguration = config ;
}
var draft = PasteAIProviderDraft ;
draft . EndpointUrl = endpoint ? . Trim ( ) ? ? string . Empty ;
SavePasteAIApiKey ( draft . Id , draft . ServiceType , apiKey ) ;
if ( _editingPasteAIProvider is null )
{
config . Providers . Add ( draft ) ;
config . ActiveProviderId ? ? = draft . Id ;
}
else
{
UpdateProviderFromDraft ( _editingPasteAIProvider , draft ) ;
_editingPasteAIProvider = null ;
}
PasteAIProviderDraft = null ;
SaveAndNotifySettings ( ) ;
OnPropertyChanged ( nameof ( PasteAIConfiguration ) ) ;
}
public void RemovePasteAIProvider ( PasteAIProviderDefinition provider )
{
if ( provider is null )
{
return ;
}
var config = PasteAIConfiguration ;
if ( config ? . Providers is null )
{
return ;
}
if ( config . Providers . Remove ( provider ) )
{
RemovePasteAICredentials ( provider . Id , provider . ServiceType ) ;
SaveAndNotifySettings ( ) ;
OnPropertyChanged ( nameof ( PasteAIConfiguration ) ) ;
}
2024-05-09 10:32:03 -04:00
}
2025-08-20 09:31:52 +08:00
protected override void Dispose ( bool disposing )
2024-05-09 10:32:03 -04:00
{
2025-08-20 09:31:52 +08:00
if ( ! _disposed )
2024-05-09 10:32:03 -04:00
{
if ( disposing )
{
2025-11-05 16:13:55 +08:00
UnsubscribeFromPasteAIConfiguration ( _advancedPasteSettings ? . Properties . PasteAIConfiguration ) ;
2025-08-20 09:31:52 +08:00
foreach ( var action in _additionalActions . GetAllActions ( ) )
{
action . PropertyChanged - = OnAdditionalActionPropertyChanged ;
}
foreach ( var customAction in _customActions )
{
customAction . PropertyChanged - = OnCustomActionPropertyChanged ;
}
_customActions . CollectionChanged - = OnCustomActionsCollectionChanged ;
2025-11-05 16:13:55 +08:00
_settingsWatcher ? . Dispose ( ) ;
_settingsWatcher = null ;
2024-05-09 10:32:03 -04:00
}
2025-08-20 09:31:52 +08:00
_disposed = true ;
2024-05-09 10:32:03 -04:00
}
2025-08-20 09:31:52 +08:00
base . Dispose ( disposing ) ;
2024-05-09 10:32:03 -04:00
}
internal void DisableAI ( )
{
2024-05-26 13:22:50 +02:00
try
{
2025-11-05 16:13:55 +08:00
bool stateChanged = false ;
if ( _advancedPasteSettings . Properties . IsAIEnabled )
{
_advancedPasteSettings . Properties . IsAIEnabled = false ;
stateChanged = true ;
}
if ( stateChanged )
{
SaveAndNotifySettings ( ) ;
}
else
{
NotifySettingsChanged ( ) ;
}
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
2024-05-26 13:22:50 +02:00
}
catch ( Exception )
{
}
2024-05-09 10:32:03 -04:00
}
2025-11-05 16:13:55 +08:00
internal void EnableAI ( )
2024-05-09 10:32:03 -04:00
{
2024-05-26 13:22:50 +02:00
try
{
2025-11-05 16:13:55 +08:00
if ( IsOnlineAIModelsDisallowedByGPO )
{
return ;
}
bool stateChanged = false ;
if ( ! _advancedPasteSettings . Properties . IsAIEnabled )
{
_advancedPasteSettings . Properties . IsAIEnabled = true ;
stateChanged = true ;
}
if ( stateChanged )
{
SaveAndNotifySettings ( ) ;
}
else
{
NotifySettingsChanged ( ) ;
}
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
}
catch ( Exception )
{
}
}
internal void SavePasteAIApiKey ( string providerId , string serviceType , string apiKey )
{
try
{
apiKey = apiKey ? . Trim ( ) ? ? string . Empty ;
serviceType = string . IsNullOrWhiteSpace ( serviceType ) ? "OpenAI" : serviceType ;
providerId ? ? = string . Empty ;
string credentialResource = GetAICredentialResource ( serviceType ) ;
string credentialUserName = GetPasteAICredentialUserName ( providerId , serviceType ) ;
string endpointCredentialUserName = GetPasteAIEndpointCredentialUserName ( providerId , serviceType ) ;
2024-12-11 10:28:44 +01:00
PasswordVault vault = new ( ) ;
2025-11-05 16:13:55 +08:00
TryRemoveCredential ( vault , credentialResource , credentialUserName ) ;
TryRemoveCredential ( vault , credentialResource , endpointCredentialUserName ) ;
bool storeApiKey = RequiresCredentialStorage ( serviceType ) & & ! string . IsNullOrWhiteSpace ( apiKey ) ;
if ( storeApiKey )
{
PasswordCredential cred = new ( credentialResource , credentialUserName , apiKey ) ;
vault . Add ( cred ) ;
}
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
2024-09-24 19:16:20 +02:00
NotifySettingsChanged ( ) ;
2024-05-26 13:22:50 +02:00
}
catch ( Exception )
{
}
2024-05-09 10:32:03 -04:00
}
2024-08-22 16:17:12 +02:00
2025-11-05 16:13:55 +08:00
internal string GetPasteAIApiKey ( string providerId , string serviceType )
{
serviceType = string . IsNullOrWhiteSpace ( serviceType ) ? "OpenAI" : serviceType ;
providerId ? ? = string . Empty ;
return RetrieveCredentialValue (
GetAICredentialResource ( serviceType ) ,
GetPasteAICredentialUserName ( providerId , serviceType ) ) ;
}
internal string GetPasteAIEndpoint ( string providerId , string serviceType )
{
providerId ? ? = string . Empty ;
var providers = PasteAIConfiguration ? . Providers ;
if ( providers is null )
{
return string . Empty ;
}
var provider = providers . FirstOrDefault ( p = > string . Equals ( p . Id ? ? string . Empty , providerId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( provider is null & & ! string . IsNullOrWhiteSpace ( serviceType ) )
{
provider = providers . FirstOrDefault ( p = > string . Equals ( p . ServiceType , serviceType , StringComparison . OrdinalIgnoreCase ) ) ;
}
return provider ? . EndpointUrl ? . Trim ( ) ? ? string . Empty ;
}
private string GetAICredentialResource ( string serviceType )
{
serviceType = string . IsNullOrWhiteSpace ( serviceType ) ? "OpenAI" : serviceType ;
return serviceType . ToLowerInvariant ( ) switch
{
"openai" = > "https://platform.openai.com/api-keys" ,
"azureopenai" = > "https://azure.microsoft.com/products/ai-services/openai-service" ,
"azureaiinference" = > "https://azure.microsoft.com/products/ai-services/ai-inference" ,
"mistral" = > "https://console.mistral.ai/account/api-keys" ,
"google" = > "https://ai.google.dev/" ,
"ollama" = > "https://ollama.com/" ,
_ = > "https://platform.openai.com/api-keys" ,
} ;
}
private string GetPasteAICredentialUserName ( string providerId , string serviceType )
{
serviceType = string . IsNullOrWhiteSpace ( serviceType ) ? "OpenAI" : serviceType ;
providerId ? ? = string . Empty ;
string service = serviceType . ToLowerInvariant ( ) ;
string normalizedId = NormalizeProviderIdentifier ( providerId ) ;
return $"PowerToys_AdvancedPaste_PasteAI_{service}_{normalizedId}" ;
}
private string GetPasteAIEndpointCredentialUserName ( string providerId , string serviceType )
{
return GetPasteAICredentialUserName ( providerId , serviceType ) + "_Endpoint" ;
}
private static void UpdateProviderFromDraft ( PasteAIProviderDefinition target , PasteAIProviderDefinition source )
{
if ( target is null | | source is null )
{
return ;
}
target . ServiceType = source . ServiceType ;
target . ModelName = source . ModelName ;
target . EndpointUrl = source . EndpointUrl ;
target . ApiVersion = source . ApiVersion ;
target . DeploymentName = source . DeploymentName ;
target . ModelPath = source . ModelPath ;
target . SystemPrompt = source . SystemPrompt ;
target . ModerationEnabled = source . ModerationEnabled ;
target . EnableAdvancedAI = source . EnableAdvancedAI ;
target . IsLocalModel = source . IsLocalModel ;
}
private void RemovePasteAICredentials ( string providerId , string serviceType )
{
try
{
serviceType = string . IsNullOrWhiteSpace ( serviceType ) ? "OpenAI" : serviceType ;
providerId ? ? = string . Empty ;
string credentialResource = GetAICredentialResource ( serviceType ) ;
PasswordVault vault = new ( ) ;
TryRemoveCredential ( vault , credentialResource , GetPasteAICredentialUserName ( providerId , serviceType ) ) ;
TryRemoveCredential ( vault , credentialResource , GetPasteAIEndpointCredentialUserName ( providerId , serviceType ) ) ;
}
catch ( Exception )
{
}
}
private static string NormalizeProviderIdentifier ( string providerId )
{
if ( string . IsNullOrWhiteSpace ( providerId ) )
{
return "default" ;
}
var filtered = new string ( providerId . Where ( char . IsLetterOrDigit ) . ToArray ( ) ) ;
return string . IsNullOrWhiteSpace ( filtered ) ? "default" : filtered . ToLowerInvariant ( ) ;
}
private static bool RequiresCredentialStorage ( string serviceType )
{
var serviceTypeKind = serviceType . ToAIServiceType ( ) ;
return serviceTypeKind switch
{
AIServiceType . Onnx = > false ,
AIServiceType . Ollama = > false ,
AIServiceType . FoundryLocal = > false ,
AIServiceType . ML = > false ,
_ = > true ,
} ;
}
private static void TryRemoveCredential ( PasswordVault vault , string credentialResource , string credentialUserName )
{
try
{
PasswordCredential existingCred = vault . Retrieve ( credentialResource , credentialUserName ) ;
vault . Remove ( existingCred ) ;
}
catch ( Exception )
{
// Credential doesn't exist, which is fine
}
}
2024-08-22 16:17:12 +02:00
internal AdvancedPasteCustomAction GetNewCustomAction ( string namePrefix )
{
ArgumentException . ThrowIfNullOrEmpty ( namePrefix ) ;
var maxUsedPrefix = _customActions . Select ( customAction = > customAction . Name )
. Where ( name = > name . StartsWith ( namePrefix , StringComparison . InvariantCulture ) )
. Select ( name = > int . TryParse ( name . AsSpan ( namePrefix . Length ) , out int number ) ? number : 0 )
. DefaultIfEmpty ( 0 )
. Max ( ) ;
var maxUsedId = _customActions . Select ( customAction = > customAction . Id )
. DefaultIfEmpty ( - 1 )
. Max ( ) ;
return new ( )
{
Id = maxUsedId + 1 ,
Name = $"{namePrefix} {maxUsedPrefix + 1}" ,
IsShown = true ,
} ;
}
internal void AddCustomAction ( AdvancedPasteCustomAction customAction )
{
if ( _customActions . Any ( existingCustomAction = > existingCustomAction . Id = = customAction . Id ) )
{
throw new ArgumentException ( "Duplicate custom action" , nameof ( customAction ) ) ;
}
_customActions . Add ( customAction ) ;
}
internal void DeleteCustomAction ( AdvancedPasteCustomAction customAction ) = > _customActions . Remove ( customAction ) ;
private void SaveCustomActions ( ) = > SaveAndNotifySettings ( ) ;
private void SaveAndNotifySettings ( )
{
2025-11-05 16:13:55 +08:00
if ( _suppressSave )
{
return ;
}
2024-08-22 16:17:12 +02:00
_settingsUtils . SaveSettings ( _advancedPasteSettings . ToJsonString ( ) , AdvancedPasteSettings . ModuleName ) ;
NotifySettingsChanged ( ) ;
}
2024-10-18 15:34:09 +02:00
private void OnAdditionalActionPropertyChanged ( object sender , PropertyChangedEventArgs e )
{
SaveAndNotifySettings ( ) ;
if ( e . PropertyName = = nameof ( AdvancedPasteAdditionalAction . Shortcut ) )
{
OnPropertyChanged ( nameof ( IsAdditionalActionConflictingCopyShortcut ) ) ;
}
}
2024-08-22 16:17:12 +02:00
private void OnCustomActionPropertyChanged ( object sender , PropertyChangedEventArgs e )
{
2025-11-11 20:07:41 +08:00
if ( ! string . IsNullOrEmpty ( e . PropertyName ) & & ! CustomActionNonPersistedProperties . Contains ( e . PropertyName ) )
2024-08-22 16:17:12 +02:00
{
SaveCustomActions ( ) ;
}
if ( e . PropertyName = = nameof ( AdvancedPasteCustomAction . Shortcut ) )
{
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
}
}
private void OnCustomActionsCollectionChanged ( object sender , NotifyCollectionChangedEventArgs e )
{
void AddRange ( System . Collections . IList items )
{
foreach ( AdvancedPasteCustomAction item in items )
{
item . PropertyChanged + = OnCustomActionPropertyChanged ;
}
}
void RemoveRange ( System . Collections . IList items )
{
foreach ( AdvancedPasteCustomAction item in items )
{
item . PropertyChanged - = OnCustomActionPropertyChanged ;
}
}
switch ( e . Action )
{
case NotifyCollectionChangedAction . Add :
AddRange ( e . NewItems ) ;
break ;
case NotifyCollectionChangedAction . Remove :
RemoveRange ( e . OldItems ) ;
break ;
case NotifyCollectionChangedAction . Replace :
AddRange ( e . NewItems ) ;
RemoveRange ( e . OldItems ) ;
break ;
case NotifyCollectionChangedAction . Move :
break ;
default :
throw new ArgumentException ( $"Unsupported {nameof(e.Action)} {e.Action}" , nameof ( e ) ) ;
}
OnPropertyChanged ( nameof ( IsConflictingCopyShortcut ) ) ;
UpdateCustomActionsCanMoveUpDown ( ) ;
SaveCustomActions ( ) ;
}
2025-11-05 16:13:55 +08:00
private void AttachConfigurationHandlers ( )
{
SubscribeToPasteAIConfiguration ( _advancedPasteSettings . Properties . PasteAIConfiguration ) ;
}
private void SetupSettingsFileWatcher ( )
{
_settingsWatcher = Helper . GetFileWatcher ( AdvancedPasteSettings . ModuleName , SettingsUtils . DefaultFileName , OnSettingsFileChanged ) ;
}
private void OnSettingsFileChanged ( )
{
if ( _disposed )
{
return ;
}
void Handler ( )
{
ApplyExternalSettings ( ) ;
}
if ( _dispatcherQueue is not null & & ! _dispatcherQueue . HasThreadAccess )
{
_dispatcherQueue . TryEnqueue ( DispatcherQueuePriority . Normal , Handler ) ;
}
else
{
Handler ( ) ;
}
}
private void ApplyExternalSettings ( )
{
if ( _disposed )
{
return ;
}
AdvancedPasteSettings latestSettings ;
try
{
latestSettings = _settingsUtils . GetSettingsOrDefault < AdvancedPasteSettings > ( AdvancedPasteSettings . ModuleName ) ;
}
catch
{
return ;
}
if ( latestSettings ? . Properties is null )
{
return ;
}
try
{
_suppressSave = true ;
ApplyExternalProperties ( latestSettings . Properties ) ;
}
finally
{
_suppressSave = false ;
}
}
private void ApplyExternalProperties ( AdvancedPasteProperties source )
{
var target = _advancedPasteSettings ? . Properties ;
if ( target is null | | source is null )
{
return ;
}
if ( target . IsAIEnabled ! = source . IsAIEnabled )
{
target . IsAIEnabled = source . IsAIEnabled ;
OnPropertyChanged ( nameof ( IsAIEnabled ) ) ;
}
if ( target . ShowCustomPreview ! = source . ShowCustomPreview )
{
target . ShowCustomPreview = source . ShowCustomPreview ;
OnPropertyChanged ( nameof ( ShowCustomPreview ) ) ;
}
if ( target . CloseAfterLosingFocus ! = source . CloseAfterLosingFocus )
{
target . CloseAfterLosingFocus = source . CloseAfterLosingFocus ;
OnPropertyChanged ( nameof ( CloseAfterLosingFocus ) ) ;
}
2025-11-13 10:18:13 +08:00
if ( target . EnableClipboardPreview ! = source . EnableClipboardPreview )
{
target . EnableClipboardPreview = source . EnableClipboardPreview ;
OnPropertyChanged ( nameof ( EnableClipboardPreview ) ) ;
}
2025-11-05 16:13:55 +08:00
var incomingConfig = source . PasteAIConfiguration ? ? new PasteAIConfiguration ( ) ;
if ( ShouldReplacePasteAIConfiguration ( target . PasteAIConfiguration , incomingConfig ) )
{
PasteAIConfiguration = incomingConfig ;
}
}
private static bool ShouldReplacePasteAIConfiguration ( PasteAIConfiguration current , PasteAIConfiguration incoming )
{
if ( incoming is null )
{
return false ;
}
if ( current is null )
{
return true ;
}
if ( ! string . Equals ( current . ActiveProviderId ? ? string . Empty , incoming . ActiveProviderId ? ? string . Empty , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
var currentProviders = current . Providers ? ? new ObservableCollection < PasteAIProviderDefinition > ( ) ;
var incomingProviders = incoming . Providers ? ? new ObservableCollection < PasteAIProviderDefinition > ( ) ;
if ( currentProviders . Count ! = incomingProviders . Count )
{
return true ;
}
for ( int i = 0 ; i < currentProviders . Count ; i + + )
{
var existing = currentProviders [ i ] ;
var updated = incomingProviders [ i ] ;
if ( ! string . Equals ( existing ? . Id ? ? string . Empty , updated ? . Id ? ? string . Empty , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . ServiceType ? ? string . Empty , updated ? . ServiceType ? ? string . Empty , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . ModelName ? ? string . Empty , updated ? . ModelName ? ? string . Empty , StringComparison . Ordinal ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . EndpointUrl ? ? string . Empty , updated ? . EndpointUrl ? ? string . Empty , StringComparison . Ordinal ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . DeploymentName ? ? string . Empty , updated ? . DeploymentName ? ? string . Empty , StringComparison . Ordinal ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . ApiVersion ? ? string . Empty , updated ? . ApiVersion ? ? string . Empty , StringComparison . Ordinal ) )
{
return true ;
}
if ( ! string . Equals ( existing ? . SystemPrompt ? ? string . Empty , updated ? . SystemPrompt ? ? string . Empty , StringComparison . Ordinal ) )
{
return true ;
}
if ( existing ? . ModerationEnabled ! = updated ? . ModerationEnabled | | existing ? . EnableAdvancedAI ! = updated ? . EnableAdvancedAI | | existing ? . IsActive ! = updated ? . IsActive )
{
return true ;
}
}
return false ;
}
private void SubscribeToPasteAIConfiguration ( PasteAIConfiguration configuration )
{
if ( configuration is not null )
{
configuration . PropertyChanged + = OnPasteAIConfigurationPropertyChanged ;
SubscribeToPasteAIProviders ( configuration ) ;
}
}
private void UnsubscribeFromPasteAIConfiguration ( PasteAIConfiguration configuration )
{
if ( configuration is not null )
{
configuration . PropertyChanged - = OnPasteAIConfigurationPropertyChanged ;
UnsubscribeFromPasteAIProviders ( configuration ) ;
}
}
private void SubscribeToPasteAIProviders ( PasteAIConfiguration configuration )
{
if ( configuration ? . Providers is null )
{
return ;
}
configuration . Providers . CollectionChanged - = OnPasteAIProvidersCollectionChanged ;
configuration . Providers . CollectionChanged + = OnPasteAIProvidersCollectionChanged ;
foreach ( var provider in configuration . Providers )
{
provider . PropertyChanged - = OnPasteAIProviderPropertyChanged ;
provider . PropertyChanged + = OnPasteAIProviderPropertyChanged ;
}
}
private void UnsubscribeFromPasteAIProviders ( PasteAIConfiguration configuration )
{
if ( configuration ? . Providers is null )
{
return ;
}
configuration . Providers . CollectionChanged - = OnPasteAIProvidersCollectionChanged ;
foreach ( var provider in configuration . Providers )
{
provider . PropertyChanged - = OnPasteAIProviderPropertyChanged ;
}
}
private void OnPasteAIProvidersCollectionChanged ( object sender , NotifyCollectionChangedEventArgs e )
{
if ( e ? . NewItems is not null )
{
foreach ( PasteAIProviderDefinition provider in e . NewItems )
{
provider . PropertyChanged + = OnPasteAIProviderPropertyChanged ;
}
}
if ( e ? . OldItems is not null )
{
foreach ( PasteAIProviderDefinition provider in e . OldItems )
{
provider . PropertyChanged - = OnPasteAIProviderPropertyChanged ;
}
}
var pasteConfig = _advancedPasteSettings ? . Properties ? . PasteAIConfiguration ;
OnPropertyChanged ( nameof ( PasteAIConfiguration ) ) ;
SaveAndNotifySettings ( ) ;
}
private void OnPasteAIProviderPropertyChanged ( object sender , PropertyChangedEventArgs e )
{
if ( sender is PasteAIProviderDefinition provider )
{
// When service type changes we may need to update credentials entry names.
if ( string . Equals ( e . PropertyName , nameof ( PasteAIProviderDefinition . ServiceType ) , StringComparison . Ordinal ) )
{
SaveAndNotifySettings ( ) ;
return ;
}
SaveAndNotifySettings ( ) ;
}
}
private void OnPasteAIConfigurationPropertyChanged ( object sender , PropertyChangedEventArgs e )
{
if ( string . Equals ( e . PropertyName , nameof ( PasteAIConfiguration . Providers ) , StringComparison . Ordinal ) )
{
SubscribeToPasteAIProviders ( PasteAIConfiguration ) ;
SaveAndNotifySettings ( ) ;
return ;
}
2025-11-14 16:56:23 +08:00
if ( string . Equals ( e . PropertyName , nameof ( PasteAIConfiguration . ActiveProviderId ) , StringComparison . Ordinal ) )
2025-11-05 16:13:55 +08:00
{
SaveAndNotifySettings ( ) ;
}
}
private void InitializePasteAIProviderState ( )
{
var pasteConfig = _advancedPasteSettings ? . Properties ? . PasteAIConfiguration ;
if ( pasteConfig is null )
{
_advancedPasteSettings . Properties . PasteAIConfiguration = new PasteAIConfiguration ( ) ;
pasteConfig = _advancedPasteSettings . Properties . PasteAIConfiguration ;
}
pasteConfig . Providers ? ? = new ObservableCollection < PasteAIProviderDefinition > ( ) ;
2025-11-12 16:24:21 +08:00
2025-11-05 16:13:55 +08:00
SubscribeToPasteAIProviders ( pasteConfig ) ;
}
private static string RetrieveCredentialValue ( string credentialResource , string credentialUserName )
{
if ( string . IsNullOrWhiteSpace ( credentialResource ) | | string . IsNullOrWhiteSpace ( credentialUserName ) )
{
return string . Empty ;
}
try
{
PasswordVault vault = new ( ) ;
PasswordCredential existingCred = vault . Retrieve ( credentialResource , credentialUserName ) ;
existingCred ? . RetrievePassword ( ) ;
return existingCred ? . Password ? . Trim ( ) ? ? string . Empty ;
}
catch ( Exception )
{
return string . Empty ;
}
}
2024-08-22 16:17:12 +02:00
private void UpdateCustomActionsCanMoveUpDown ( )
{
for ( int index = 0 ; index < _customActions . Count ; index + + )
{
var customAction = _customActions [ index ] ;
customAction . CanMoveUp = index ! = 0 ;
customAction . CanMoveDown = index ! = _customActions . Count - 1 ;
}
}
2024-05-09 10:32:03 -04:00
}
}