Advanced Paste: AI pasting enhancement (#42374)

<!-- 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>
This commit is contained in:
Shawn Yuan
2025-11-05 16:13:55 +08:00
committed by GitHub
parent c364aa7c70
commit a3b8dc6cb8
119 changed files with 8441 additions and 958 deletions

View File

@@ -2,7 +2,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h AdvancedPaste.base.rc AdvancedPaste.rc" />
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted ..\..\..\..\tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h AdvancedPaste.base.rc AdvancedPaste.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>

View File

@@ -16,7 +16,8 @@
#include <common/utils/winapi_error.h>
#include <common/utils/gpo.h>
#include <winrt/Windows.Security.Credentials.h>
#include <algorithm>
#include <cwctype>
#include <vector>
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -54,12 +55,14 @@ namespace
const wchar_t JSON_KEY_ADVANCED_PASTE_UI_HOTKEY[] = L"advanced-paste-ui-hotkey";
const wchar_t JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY[] = L"paste-as-markdown-hotkey";
const wchar_t JSON_KEY_PASTE_AS_JSON_HOTKEY[] = L"paste-as-json-hotkey";
const wchar_t JSON_KEY_IS_ADVANCED_AI_ENABLED[] = L"IsAdvancedAIEnabled";
const wchar_t JSON_KEY_IS_AI_ENABLED[] = L"IsAIEnabled";
const wchar_t JSON_KEY_IS_OPEN_AI_ENABLED[] = L"IsOpenAIEnabled";
const wchar_t JSON_KEY_SHOW_CUSTOM_PREVIEW[] = L"ShowCustomPreview";
const wchar_t JSON_KEY_PASTE_AI_CONFIGURATION[] = L"paste-ai-configuration";
const wchar_t JSON_KEY_PROVIDERS[] = L"providers";
const wchar_t JSON_KEY_SERVICE_TYPE[] = L"service-type";
const wchar_t JSON_KEY_ENABLE_ADVANCED_AI[] = L"enable-advanced-ai";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t OPENAI_VAULT_RESOURCE[] = L"https://platform.openai.com/api-keys";
const wchar_t OPENAI_VAULT_USERNAME[] = L"PowerToys_AdvancedPaste_OpenAIKey";
}
class AdvancedPaste : public PowertoyModuleIface
@@ -94,6 +97,7 @@ private:
using CustomAction = ActionData<int>;
std::vector<CustomAction> m_custom_actions;
bool m_is_ai_enabled = false;
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
@@ -145,32 +149,11 @@ private:
return jsonObject;
}
static bool open_ai_key_exists()
{
try
{
winrt::Windows::Security::Credentials::PasswordVault().Retrieve(OPENAI_VAULT_RESOURCE, OPENAI_VAULT_USERNAME);
return true;
}
catch (const winrt::hresult_error& ex)
{
// Looks like the only way to access the PasswordVault is through an API that throws an exception in case the resource doesn't exist.
// If the debugger breaks here, just continue.
// If you want to disable breaking here in a more permanent way, just add a condition in Visual Studio's Exception Settings to not break on win::hresult_error, but that might make you not hit other exceptions you might want to catch.
if (ex.code() == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
{
return false; // Credential doesn't exist.
}
Logger::error("Unexpected error while retrieving OpenAI key from vault: {}", winrt::to_string(ex.message()));
return false;
}
}
bool is_open_ai_enabled()
bool is_ai_enabled()
{
return gpo_policy_enabled_configuration() != powertoys_gpo::gpo_rule_configured_disabled &&
powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue() != powertoys_gpo::gpo_rule_configured_disabled &&
open_ai_key_exists();
m_is_ai_enabled;
}
static std::wstring kebab_to_pascal_case(const std::wstring& kebab_str)
@@ -201,6 +184,13 @@ private:
return result;
}
static std::wstring to_lower_case(const std::wstring& value)
{
std::wstring result = value;
std::transform(result.begin(), result.end(), result.begin(), [](wchar_t ch) { return std::towlower(ch); });
return result;
}
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
@@ -267,6 +257,61 @@ private:
}
}
bool has_advanced_ai_provider(const winrt::Windows::Data::Json::JsonObject& propertiesObject)
{
if (!propertiesObject.HasKey(JSON_KEY_PASTE_AI_CONFIGURATION))
{
return false;
}
const auto configValue = propertiesObject.GetNamedValue(JSON_KEY_PASTE_AI_CONFIGURATION);
if (configValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return false;
}
const auto configObject = configValue.GetObjectW();
if (!configObject.HasKey(JSON_KEY_PROVIDERS))
{
return false;
}
const auto providersValue = configObject.GetNamedValue(JSON_KEY_PROVIDERS);
if (providersValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Array)
{
return false;
}
const auto providers = providersValue.GetArray();
for (const auto providerValue : providers)
{
if (providerValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
continue;
}
const auto providerObject = providerValue.GetObjectW();
if (!providerObject.GetNamedBoolean(JSON_KEY_ENABLE_ADVANCED_AI, false))
{
continue;
}
if (!providerObject.HasKey(JSON_KEY_SERVICE_TYPE))
{
continue;
}
const std::wstring serviceType = providerObject.GetNamedString(JSON_KEY_SERVICE_TYPE, L"").c_str();
const auto normalizedServiceType = to_lower_case(serviceType);
if (normalizedServiceType == L"openai" || normalizedServiceType == L"azureopenai")
{
return true;
}
}
return false;
}
void read_settings(PowerToysSettings::PowerToyValues& settings)
{
const auto settingsObject = settings.get_raw_json();
@@ -341,7 +386,7 @@ private:
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
if (customActions.Size() > 0 && is_open_ai_enabled())
if (customActions.Size() > 0 && is_ai_enabled())
{
for (const auto& customAction : customActions)
{
@@ -365,9 +410,19 @@ private:
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_IS_ADVANCED_AI_ENABLED))
m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject);
if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED))
{
m_is_advanced_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_ADVANCED_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE);
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
}
else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED))
{
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
}
else
{
m_is_ai_enabled = false;
}
if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))