mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 19:57:57 +01:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request * Add multiple endpoint support for paste with AI * Add Local AI support for paste AI * Advanced AI implementation <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #32960 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [x] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [x] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed ### GPO - [x] Paste with AI should not be available if the original GPO for paste AI is set to false - [x] Paste with AI should be controlled within endpoint granularity - [x] Advanced Paste UI should disable AI ability if GPO is set to disable for any llm ### Paste AI - [x] Every AI endpoint should work as expected - [x] Default prompt should be able to give a reasonable result - [x] Local AI should work as expected ### Advanced AI - [x] Open AI and Azure OPENAI should be able to configure as advanced AI endpoint - [x] Advanced AI should be able to pick up functions correctly to do the transformation and give reasonable result --------- Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com> Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com> Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com> Co-authored-by: Leilei Zhang <leilzh@microsoft.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Kai Tao <kaitao@microsoft.com> Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com> Co-authored-by: vanzue <vanzue@outlook.com> Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
219 lines
8.0 KiB
C#
219 lines
8.0 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
using AdvancedPaste.Helpers;
|
|
using AdvancedPaste.Models;
|
|
using AdvancedPaste.ViewModels;
|
|
using ManagedCommon;
|
|
using Microsoft.PowerToys.Telemetry;
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Microsoft.UI.Xaml.Media.Imaging;
|
|
using Windows.ApplicationModel.DataTransfer;
|
|
using Windows.Storage.Streams;
|
|
using Windows.System;
|
|
|
|
namespace AdvancedPaste.Pages
|
|
{
|
|
public sealed partial class MainPage : Page
|
|
{
|
|
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 OptionsViewModel ViewModel { get; private set; }
|
|
|
|
public MainPage()
|
|
{
|
|
this.InitializeComponent();
|
|
|
|
ViewModel = App.GetService<OptionsViewModel>();
|
|
|
|
clipboardHistory = new ObservableCollection<ClipboardItem>();
|
|
|
|
LoadClipboardHistoryEvent(null, null);
|
|
Clipboard.HistoryChanged += LoadClipboardHistoryEvent;
|
|
}
|
|
|
|
private void LoadClipboardHistoryEvent(object sender, object e)
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
LoadClipboardHistoryAsync();
|
|
});
|
|
}
|
|
|
|
public async void LoadClipboardHistoryAsync()
|
|
{
|
|
try
|
|
{
|
|
Logger.LogTrace();
|
|
|
|
List<ClipboardItem> items = new();
|
|
|
|
if (Clipboard.IsHistoryEnabled())
|
|
{
|
|
var historyItems = await Clipboard.GetHistoryItemsAsync();
|
|
if (historyItems.Status == ClipboardHistoryItemsResultStatus.Success)
|
|
{
|
|
foreach (var item in historyItems.Items)
|
|
{
|
|
if (item.Content.Contains(StandardDataFormats.Text))
|
|
{
|
|
string text = await item.Content.GetTextAsync();
|
|
items.Add(new ClipboardItem
|
|
{
|
|
Content = text,
|
|
Format = ClipboardFormat.Text,
|
|
Timestamp = item.Timestamp,
|
|
Item = item,
|
|
});
|
|
}
|
|
else if (item.Content.Contains(StandardDataFormats.Bitmap))
|
|
{
|
|
items.Add(new ClipboardItem
|
|
{
|
|
Format = ClipboardFormat.Image,
|
|
Timestamp = item.Timestamp,
|
|
Item = item,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_dispatcherQueue.TryEnqueue(async () =>
|
|
{
|
|
// Clear to avoid leaks due to Garbage Collection not clearing the bitmap from memory. Fix for https://github.com/microsoft/PowerToys/issues/33423
|
|
clipboardHistory.Where(x => x.Image is not null)
|
|
.ToList()
|
|
.ForEach(x => x.Image.ClearValue(BitmapImage.UriSourceProperty));
|
|
|
|
clipboardHistory.Clear();
|
|
|
|
foreach (var item in items)
|
|
{
|
|
if (item.Item.Content.Contains(StandardDataFormats.Bitmap))
|
|
{
|
|
IRandomAccessStreamReference imageReceived = null;
|
|
imageReceived = await item.Item.Content.GetBitmapAsync();
|
|
if (imageReceived != null)
|
|
{
|
|
using (var imageStream = await imageReceived.OpenReadAsync())
|
|
{
|
|
var bitmapImage = new BitmapImage();
|
|
bitmapImage.SetSource(imageStream);
|
|
item.Image = bitmapImage;
|
|
}
|
|
}
|
|
}
|
|
|
|
clipboardHistory.Add(item);
|
|
}
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError("Loading clipboard history failed", ex);
|
|
}
|
|
}
|
|
|
|
private static MainWindow GetMainWindow() => (App.Current as App)?.GetMainWindow();
|
|
|
|
private void ClipboardHistoryItemDeleteButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Logger.LogTrace();
|
|
|
|
if (sender is MenuFlyoutItem btn)
|
|
{
|
|
ClipboardItem item = btn.CommandParameter as ClipboardItem;
|
|
Clipboard.DeleteItemFromHistory(item.Item);
|
|
clipboardHistory.Remove(item);
|
|
|
|
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemDeletedEvent());
|
|
}
|
|
}
|
|
|
|
private async void PasteFormat_ItemClick(object sender, ItemClickEventArgs e)
|
|
{
|
|
if (e.ClickedItem is PasteFormat format)
|
|
{
|
|
await ViewModel.ExecutePasteFormatAsync(format, PasteActionSource.ContextMenu);
|
|
}
|
|
}
|
|
|
|
private async void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
|
|
{
|
|
if (GetMainWindow()?.Visible is false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Logger.LogTrace();
|
|
|
|
var thisKeyEvent = (sender.Key, Timestamp: DateTime.Now);
|
|
if (thisKeyEvent.Key == _lastKeyEvent.Key && (thisKeyEvent.Timestamp - _lastKeyEvent.Timestamp) < TimeSpan.FromMilliseconds(200))
|
|
{
|
|
// Sometimes, multiple keyboard accelerator events are raised for a single Ctrl + VirtualKey press.
|
|
return;
|
|
}
|
|
|
|
_lastKeyEvent = thisKeyEvent;
|
|
|
|
switch (sender.Key)
|
|
{
|
|
case VirtualKey.Escape:
|
|
GetMainWindow()?.Close();
|
|
break;
|
|
|
|
case VirtualKey.Number1:
|
|
case VirtualKey.Number2:
|
|
case VirtualKey.Number3:
|
|
case VirtualKey.Number4:
|
|
case VirtualKey.Number5:
|
|
case VirtualKey.Number6:
|
|
case VirtualKey.Number7:
|
|
case VirtualKey.Number8:
|
|
case VirtualKey.Number9:
|
|
await ViewModel.ExecutePasteFormatAsync(sender.Key);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void Page_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
|
|
{
|
|
if (e.Key == VirtualKey.Escape)
|
|
{
|
|
GetMainWindow()?.Close();
|
|
}
|
|
}
|
|
|
|
private async void ClipboardHistory_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
|
|
{
|
|
if (args.InvokedItem is ClipboardItem item)
|
|
{
|
|
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemClicked());
|
|
if (!string.IsNullOrEmpty(item.Content))
|
|
{
|
|
ClipboardHelper.SetTextContent(item.Content);
|
|
}
|
|
else if (item.Image is not null)
|
|
{
|
|
RandomAccessStreamReference image = await item.Item.Content.GetBitmapAsync();
|
|
ClipboardHelper.SetImageContent(image);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|