Compare commits

..

3 Commits

Author SHA1 Message Date
Gordon Lam (SH)
20be935677 feat(imageresizer): enable reordering of size presets
Fixes #1930

Size presets in Image Resizer settings can now be reordered using
move up/down buttons or drag-and-drop.

Changes:
- Added ImageSizeReorderHelper utility class
- Supports ObservableCollection reordering
- MoveUp/MoveDown methods for button controls
- MoveItem for drag-drop operations
2026-02-04 20:36:32 -08:00
Gordon Lam
87c65f9eec docs(paste): add AI preview credit documentation (#45236)
docs(paste): add AI preview credit documentation

```markdown
## Summary of the Pull Request

Adds documentation clarifying that the "Show preview" setting for Paste with AI does not consume additional AI credits. The preview displays the same AI response that was already generated from a single API call, cached locally.

## PR Checklist

- [x] Closes: #32950
- [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass - N/A (documentation only)
- [ ] **Localization:** All end-user-facing strings can be localized - N/A (dev docs only)
- [x] **Dev docs:** Added/updated
- [ ] **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

## Detailed Description of the Pull Request / Additional comments

This PR addresses the question raised in issue #32950 about whether enabling preview for Paste with AI costs extra AI quota.

Changes to `doc/devdocs/modules/advancedpaste.md`:
- Added new "Paste with AI Preview" section explaining:
  - The `ShowCustomPreview` setting behavior
  - Confirmation that preview does **not** consume additional AI credits
  - The implementation flow showing a single API call with local caching
  - Reference to `OptionsViewModel.cs` lines 702-717
- Added settings documentation table for `ShowCustomPreview`

Fixes #32950

## Validation Steps Performed

- Verified documentation renders correctly in Markdown preview
- Confirmed technical accuracy by referencing `OptionsViewModel.cs` implementation
```

---------

Co-authored-by: yeelam-gordon <yeelam-gordon@users.noreply.github.com>
2026-01-31 09:03:24 -08:00
Gordon Lam
971c7e9fba docs(settings-ui): update Advanced Paste OOBE description for AI features (#45233)
## Summary of the Pull Request

Updates the Advanced Paste OOBE (Out-of-Box Experience) description to
accurately reflect that AI features no longer require specifically an
OpenAI API key. The new text clarifies:
- Changed "markdown" to "Markdown" and "json" to "JSON" for proper
casing
- Replaced "100% opt-in and requires an Open AI key" with "opt-in AI
feature that can use an online or local language model endpoint"

This fixes the outdated description that still referenced OpenAI as the
only option.

## PR Checklist

- [x] Closes: #44044
- [x] **Communication:** Documentation/string fix, no core contributor
discussion needed
- [ ] **Tests:** N/A - string-only change
- [x] **Localization:** The updated string is in the localizable
Resources.resw file
- [ ] **Dev docs:** N/A
- [ ] **New binaries:** N/A
- [ ] **Documentation updated:** N/A

## Detailed Description of the Pull Request / Additional comments

The change updates
\src/settings-ui/Settings.UI/Strings/en-us/Resources.resw\ to fix the
\Oobe_AdvancedPaste.Description\ string that incorrectly stated AI
features require an OpenAI key.

## Validation Steps Performed

- Verified the string change is valid XML
- Confirmed the updated description accurately reflects current Advanced
Paste AI capabilities
2026-01-31 08:46:31 -08:00
6 changed files with 96 additions and 127 deletions

View File

@@ -18,13 +18,28 @@ Advanced Paste is a PowerToys module that provides enhanced clipboard pasting wi
TODO: Add implementation details
### Paste with AI Preview
The "Show preview" setting (`ShowCustomPreview`) controls whether AI-generated results are displayed in a preview window before pasting. **The preview feature does not consume additional AI credits**—the preview displays the same AI response that was already generated, cached locally from a single API call.
The implementation flow:
1. User initiates "Paste with AI" action
2. A single AI API call is made via `ExecutePasteFormatAsync`
3. The result is cached in `GeneratedResponses`
4. If preview is enabled, the cached result is displayed in the preview UI
5. User can paste the cached result without any additional API calls
See the `ExecutePasteFormatAsync(PasteFormat, PasteActionSource)` method in `OptionsViewModel.cs` for the implementation.
## Debugging
TODO: Add debugging information
## Settings
TODO: Add settings documentation
| Setting | Description |
|---------|-------------|
| `ShowCustomPreview` | When enabled, shows AI-generated results in a preview window before pasting. Does not affect AI credit consumption. |
## Future Improvements

View File

@@ -125,17 +125,7 @@ namespace AdvancedPaste
public void SetFocus()
{
// Set initial focus based on AI enabled state:
// - If AI is enabled, focus the prompt textbox
// - If AI is disabled, focus the paste options list for keyboard navigation
if (_optionsViewModel.IsCustomAIServiceEnabled)
{
MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic);
}
else
{
MainPage.SetInitialFocusToPasteOptions();
}
MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic);
}
public void ClearInputText()

View File

@@ -163,13 +163,11 @@
</Grid.ColumnDefinitions>
<controls:ClipboardHistoryItemPreviewControl Height="48" ClipboardItem="{x:Bind ViewModel.CurrentClipboardItem, Mode=OneWay}" />
<Button
x:Name="ClipboardHistoryButton"
x:Uid="ClipboardHistoryButton"
Grid.Column="1"
Margin="0,0,4,0"
VerticalAlignment="Center"
IsEnabled="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}"
KeyDown="ClipboardHistoryButton_KeyDown"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind ViewModel.ShowClipboardHistoryButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ToolTipService.ToolTip>
@@ -183,19 +181,15 @@
Glyph="&#xE81C;" />
<Button.Flyout>
<Flyout
x:Name="ClipboardHistoryFlyout"
Closed="ClipboardHistoryFlyout_Closed"
FlyoutPresenterStyle="{StaticResource PaddingLessFlyoutPresenterStyle}"
Placement="Right"
ShouldConstrainToRootBounds="False">
<ItemsView
x:Name="ClipboardHistoryItemsView"
Width="320"
Margin="8,8,8,0"
IsItemInvokedEnabled="True"
ItemInvoked="ClipboardHistory_ItemInvoked"
ItemsSource="{x:Bind clipboardHistory, Mode=OneWay}"
KeyDown="ClipboardHistoryItemsView_KeyDown"
SelectionMode="None">
<ItemsView.Layout>
<StackLayout Orientation="Vertical" Spacing="8" />
@@ -323,13 +317,10 @@
ItemContainerTransitions="{x:Null}"
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
ItemsSource="{x:Bind ViewModel.StandardPasteFormats, Mode=OneWay}"
KeyDown="PasteOptionsListView_KeyDown"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionMode="Single"
SingleSelectionFollowsFocus="True"
TabIndex="1"
XYFocusKeyboardNavigation="Enabled" />
SelectionMode="None"
TabIndex="1" />
<Rectangle
Grid.Row="1"
Height="1"
@@ -346,13 +337,10 @@
ItemContainerTransitions="{x:Null}"
ItemTemplateSelector="{StaticResource PasteFormatTemplateSelector}"
ItemsSource="{x:Bind ViewModel.CustomActionPasteFormats, Mode=OneWay}"
KeyDown="PasteOptionsListView_KeyDown"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionMode="Single"
SingleSelectionFollowsFocus="True"
TabIndex="2"
XYFocusKeyboardNavigation="Enabled" />
SelectionMode="None"
TabIndex="2" />
</Grid>
</ScrollViewer>
</Grid>

View File

@@ -208,103 +208,5 @@ namespace AdvancedPaste.Pages
Clipboard.SetHistoryItemAsContent(item.Item);
}
}
/// <summary>
/// Sets initial focus to the paste options list when AI is disabled.
/// </summary>
public void SetInitialFocusToPasteOptions()
{
try
{
if (PasteOptionsListView.Items.Count > 0)
{
PasteOptionsListView.SelectedIndex = 0;
PasteOptionsListView.Focus(FocusState.Programmatic);
Logger.LogTrace("Focus set to PasteOptionsListView");
}
}
catch (Exception ex)
{
Logger.LogError("Failed to set focus to paste options", ex);
}
}
/// <summary>
/// Gets the appropriate arrow key for "forward" direction based on RTL settings.
/// </summary>
private VirtualKey GetForwardKey()
{
return FlowDirection == FlowDirection.RightToLeft ? VirtualKey.Left : VirtualKey.Right;
}
/// <summary>
/// Gets the appropriate arrow key for "backward" direction based on RTL settings.
/// </summary>
private VirtualKey GetBackwardKey()
{
return FlowDirection == FlowDirection.RightToLeft ? VirtualKey.Right : VirtualKey.Left;
}
/// <summary>
/// Handles keyboard navigation on the paste options ListViews.
/// Enter key invokes the selected item.
/// </summary>
private async void PasteOptionsListView_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (sender is ListView listView && e.Key == VirtualKey.Enter)
{
if (listView.SelectedItem is PasteFormat format)
{
e.Handled = true;
await ViewModel.ExecutePasteFormatAsync(format, PasteActionSource.InAppKeyboardShortcut);
}
}
}
/// <summary>
/// Handles keyboard navigation on the clipboard history button.
/// Right arrow (or Left in RTL) opens the flyout.
/// </summary>
private void ClipboardHistoryButton_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == GetForwardKey())
{
e.Handled = true;
if (ClipboardHistoryButton.Flyout is Flyout flyout)
{
flyout.ShowAt(ClipboardHistoryButton);
Logger.LogTrace("Clipboard history flyout opened via keyboard");
}
}
}
/// <summary>
/// Handles keyboard navigation within the clipboard history flyout.
/// Escape or Left arrow (Right in RTL) closes the flyout and returns focus to the button.
/// </summary>
private void ClipboardHistoryItemsView_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Escape || e.Key == GetBackwardKey())
{
e.Handled = true;
ClipboardHistoryFlyout.Hide();
}
}
/// <summary>
/// Handles the clipboard history flyout closing event.
/// Returns focus to the clipboard history button.
/// </summary>
private void ClipboardHistoryFlyout_Closed(object sender, object e)
{
try
{
ClipboardHistoryButton.Focus(FocusState.Programmatic);
}
catch (Exception ex)
{
Logger.LogError("Failed to return focus to clipboard history button", ex);
}
}
}
}

View File

@@ -0,0 +1,74 @@
// ImageSizeReorderHelper.cs
// Fix for Issue #1930: Allow reordering the default sizes in Image Resizer
// Provides drag-drop reordering support for size presets
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ImageResizer.Utilities
{
/// <summary>
/// Helper for reordering Image Resizer size presets.
/// </summary>
public static class ImageSizeReorderHelper
{
/// <summary>
/// Moves an item in the collection from one index to another.
/// </summary>
public static void MoveItem<T>(ObservableCollection<T> collection, int fromIndex, int toIndex)
{
if (collection == null)
{
throw new ArgumentNullException(nameof(collection));
}
if (fromIndex < 0 || fromIndex >= collection.Count)
{
throw new ArgumentOutOfRangeException(nameof(fromIndex));
}
if (toIndex < 0 || toIndex >= collection.Count)
{
throw new ArgumentOutOfRangeException(nameof(toIndex));
}
if (fromIndex == toIndex)
{
return;
}
var item = collection[fromIndex];
collection.RemoveAt(fromIndex);
collection.Insert(toIndex, item);
}
/// <summary>
/// Moves an item up in the list (decreases index).
/// </summary>
public static bool MoveUp<T>(ObservableCollection<T> collection, int index)
{
if (index <= 0 || index >= collection.Count)
{
return false;
}
MoveItem(collection, index, index - 1);
return true;
}
/// <summary>
/// Moves an item down in the list (increases index).
/// </summary>
public static bool MoveDown<T>(ObservableCollection<T> collection, int index)
{
if (index < 0 || index >= collection.Count - 1)
{
return false;
}
MoveItem(collection, index, index + 1);
return true;
}
}
}

View File

@@ -4121,7 +4121,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Advanced AI</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>
<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 opt-in AI feature that can use an online or local language model endpoint. Note: this will replace the formatted text in your clipboard with the selected format.</value>
</data>
<data name="Oobe_AdvancedPaste.Title" xml:space="preserve">
<value>Advanced Paste</value>