Compare commits

..

3 Commits

Author SHA1 Message Date
Gordon Lam (SH)
002128aefc Failed to read MCP config file ".github/skills/submit-pr/references/mcp-config.json": ENOENT: no such file or directory, open 'Q:\PowerToys-ed64\.github\skills\submit-pr\references\mcp-config.json' 2026-02-01 18:10:42 -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
5 changed files with 18 additions and 199 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

File diff suppressed because one or more lines are too long

View File

@@ -1,167 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Reflection;
using AdvancedPaste.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AdvancedPaste.UnitTests.HelpersTests;
[TestClass]
public sealed class MarkdownHelperTests
{
/// <summary>
/// Helper method to invoke the private CleanHtml method for testing.
/// </summary>
private static string InvokeCleanHtml(string html)
{
var methodInfo = typeof(MarkdownHelper).GetMethod("CleanHtml", BindingFlags.NonPublic | BindingFlags.Static);
return (string)methodInfo!.Invoke(null, [html])!;
}
/// <summary>
/// Helper method to invoke the private ConvertHtmlToMarkdown method for testing.
/// </summary>
private static string InvokeConvertHtmlToMarkdown(string html)
{
var methodInfo = typeof(MarkdownHelper).GetMethod("ConvertHtmlToMarkdown", BindingFlags.NonPublic | BindingFlags.Static);
return (string)methodInfo!.Invoke(null, [html])!;
}
[TestMethod]
public void CleanHtml_GoogleSheetsWrapper_RemovesWrapperPreservesTable()
{
// Arrange - Google Sheets HTML with wrapper element
const string googleSheetsHtml = @"<google-sheets-html-origin>
<style type=""text/css""><!--td {border: 1px solid #cccccc;}--></style>
<table xmlns=""http://www.w3.org/1999/xhtml"" cellspacing=""0"" cellpadding=""0"" dir=""ltr"" border=""1"" data-sheets-root=""1"">
<colgroup><col width=""100""><col width=""100""></colgroup>
<tbody><tr><td>A</td><td>B</td></tr><tr><td>1</td><td>2</td></tr></tbody>
</table>
</google-sheets-html-origin>";
// Act
string cleanedHtml = InvokeCleanHtml(googleSheetsHtml);
// Assert - wrapper and style should be removed, table preserved
Assert.IsFalse(cleanedHtml.Contains("google-sheets-html-origin"), "Google Sheets wrapper should be removed");
Assert.IsFalse(cleanedHtml.Contains("<style"), "Style element should be removed");
Assert.IsFalse(cleanedHtml.Contains("<colgroup"), "Colgroup element should be removed");
Assert.IsTrue(cleanedHtml.Contains("<table"), "Table element should be preserved");
Assert.IsTrue(cleanedHtml.Contains("<td>A</td>"), "Table content should be preserved");
}
[TestMethod]
public void CleanHtml_GoogleSheetsHtml_ConvertsToMarkdownTable()
{
// Arrange - Google Sheets HTML with wrapper element
const string googleSheetsHtml = @"<google-sheets-html-origin>
<style type=""text/css""><!--td {border: 1px solid #cccccc;}--></style>
<table xmlns=""http://www.w3.org/1999/xhtml"" cellspacing=""0"" cellpadding=""0"" dir=""ltr"" border=""1"" data-sheets-root=""1"">
<colgroup><col width=""100""><col width=""100""></colgroup>
<tbody><tr><td>A</td><td>B</td></tr><tr><td>1</td><td>2</td></tr></tbody>
</table>
</google-sheets-html-origin>";
// Act
string cleanedHtml = InvokeCleanHtml(googleSheetsHtml);
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
// Assert - should produce valid Markdown table
Assert.IsTrue(markdown.Contains("|"), "Markdown should contain table pipes");
Assert.IsTrue(markdown.Contains("A") && markdown.Contains("B"), "Markdown should contain table content");
Assert.IsTrue(markdown.Contains("1") && markdown.Contains("2"), "Markdown should contain table data");
Assert.IsFalse(markdown.Contains("<google-sheets-html-origin>"), "Markdown should not contain HTML wrapper");
}
[TestMethod]
public void CleanHtml_ExcelHtml_ConvertsToMarkdownTable()
{
// Arrange - Typical Excel HTML (for regression testing)
const string excelHtml = @"<table border=""0"" cellpadding=""0"" cellspacing=""0"" width=""192"">
<tbody><tr height=""20""><td height=""20"" width=""64"">Name</td><td width=""64"">Value</td><td width=""64"">Status</td></tr>
<tr height=""20""><td height=""20"">Item1</td><td>100</td><td>Active</td></tr>
<tr height=""20""><td height=""20"">Item2</td><td>200</td><td>Inactive</td></tr>
</tbody></table>";
// Act
string cleanedHtml = InvokeCleanHtml(excelHtml);
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
// Assert - Excel HTML should still convert correctly
Assert.IsTrue(markdown.Contains("|"), "Markdown should contain table pipes");
Assert.IsTrue(markdown.Contains("Name"), "Markdown should contain header content");
Assert.IsTrue(markdown.Contains("Item1") && markdown.Contains("Item2"), "Markdown should contain row data");
}
[TestMethod]
public void CleanHtml_StyleElement_IsRemoved()
{
// Arrange
const string htmlWithStyle = @"<html><head><style>body { color: red; }</style></head><body><p>Text</p></body></html>";
// Act
string cleanedHtml = InvokeCleanHtml(htmlWithStyle);
// Assert
Assert.IsFalse(cleanedHtml.Contains("<style"), "Style element should be removed");
Assert.IsTrue(cleanedHtml.Contains("<p>Text</p>") || cleanedHtml.Contains("Text"), "Content should be preserved");
}
[TestMethod]
public void CleanHtml_ColgroupElement_IsRemoved()
{
// Arrange
const string htmlWithColgroup = @"<table><colgroup><col width=""100""></colgroup><tbody><tr><td>Data</td></tr></tbody></table>";
// Act
string cleanedHtml = InvokeCleanHtml(htmlWithColgroup);
// Assert
Assert.IsFalse(cleanedHtml.Contains("<colgroup"), "Colgroup element should be removed");
Assert.IsTrue(cleanedHtml.Contains("<table"), "Table element should be preserved");
Assert.IsTrue(cleanedHtml.Contains("Data"), "Table content should be preserved");
}
[TestMethod]
public void CleanHtml_ScriptElement_IsRemoved()
{
// Arrange
const string htmlWithScript = @"<html><body><script>alert('test');</script><p>Content</p></body></html>";
// Act
string cleanedHtml = InvokeCleanHtml(htmlWithScript);
// Assert
Assert.IsFalse(cleanedHtml.Contains("<script"), "Script element should be removed");
Assert.IsTrue(cleanedHtml.Contains("Content"), "Content should be preserved");
}
[TestMethod]
public void CleanHtml_NestedGoogleSheetsTable_PreservesNestedContent()
{
// Arrange - More complex Google Sheets HTML
const string complexGoogleSheetsHtml = @"<google-sheets-html-origin>
<style type=""text/css"">td {border: 1px solid #ccc;}</style>
<table data-sheets-root=""1"">
<colgroup><col width=""100""><col width=""150""><col width=""100""></colgroup>
<tbody>
<tr><td>Header1</td><td>Header2</td><td>Header3</td></tr>
<tr><td>Row1Col1</td><td>Row1Col2</td><td>Row1Col3</td></tr>
<tr><td>Row2Col1</td><td>Row2Col2</td><td>Row2Col3</td></tr>
</tbody>
</table>
</google-sheets-html-origin>";
// Act
string cleanedHtml = InvokeCleanHtml(complexGoogleSheetsHtml);
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
// Assert
Assert.IsFalse(markdown.Contains("<google-sheets-html-origin>"), "Wrapper should be removed from markdown");
Assert.IsTrue(markdown.Contains("Header1"), "Headers should be preserved");
Assert.IsTrue(markdown.Contains("Row1Col1"), "Row data should be preserved");
Assert.IsTrue(markdown.Contains("Row2Col3"), "All cells should be preserved");
}
}

View File

@@ -57,23 +57,6 @@ namespace AdvancedPaste.Helpers
{
Logger.LogTrace();
// Unwrap Google Sheets wrapper element (preserve children, remove wrapper)
foreach (var googleSheetsWrapper in node.DescendantsAndSelf("google-sheets-html-origin").ToArray())
{
var parent = googleSheetsWrapper.ParentNode;
if (parent == null)
{
continue;
}
foreach (var child in googleSheetsWrapper.ChildNodes.ToArray())
{
parent.InsertBefore(child, googleSheetsWrapper);
}
googleSheetsWrapper.Remove();
}
// Remove specific elements by tag name, CSS class, or other attributes
// Example: Remove all <script> elements
foreach (var scriptNode in node.DescendantsAndSelf("script").ToArray())
@@ -81,18 +64,6 @@ namespace AdvancedPaste.Helpers
scriptNode.Remove();
}
// Remove style elements (CSS not relevant for Markdown)
foreach (var styleNode in node.DescendantsAndSelf("style").ToArray())
{
styleNode.Remove();
}
// Remove colgroup elements (column width info not needed for Markdown)
foreach (var colgroupNode in node.DescendantsAndSelf("colgroup").ToArray())
{
colgroupNode.Remove();
}
// Ignore specific elements like <sup> elements
foreach (var ignoredElement in node.DescendantsAndSelf("sup").ToArray())
{

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>