diff --git a/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs b/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs index 64da1c35..2d3e57f7 100644 --- a/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs +++ b/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs @@ -10,5 +10,6 @@ internal enum HardwareAccelerator { CPU, DML, - QNN + QNN, + WCRAPI } \ No newline at end of file diff --git a/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs b/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs index dd319abe..913a0ea2 100644 --- a/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs +++ b/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs @@ -52,7 +52,7 @@ private void ExecuteSharedCodeEnumGeneration(SourceProductionContext context, Im { var filePath = type!.Locations[0].SourceTree?.FilePath; - if (filePath != null) + if (filePath != null && !filePath.Contains(@"\obj\")) { if (!filePaths.Contains(filePath)) { @@ -66,7 +66,15 @@ private void ExecuteSharedCodeEnumGeneration(SourceProductionContext context, Im foreach (var filePath in filePaths) { var fileName = Path.GetFileNameWithoutExtension(filePath); - if (File.Exists(Path.ChangeExtension(filePath, ".xaml"))) + var extension = Path.GetExtension(filePath); + var filePathWithoutExtension = filePath.Substring(0, filePath.Length - extension.Length); + if (fileName.EndsWith(".xaml", StringComparison.InvariantCultureIgnoreCase) && File.Exists(filePathWithoutExtension)) + { + fileName = Path.GetFileNameWithoutExtension(filePathWithoutExtension); + sourceBuilder.AppendLine($" {fileName}Cs,"); + sourceBuilder.AppendLine($" {fileName}Xaml,"); + } + else if (File.Exists(Path.ChangeExtension(filePath, ".xaml"))) { sourceBuilder.AppendLine($" {fileName}Cs,"); sourceBuilder.AppendLine($" {fileName}Xaml,"); @@ -89,14 +97,22 @@ private void ExecuteSharedCodeEnumGeneration(SourceProductionContext context, Im { var fileName = Path.GetFileNameWithoutExtension(filePath); var filePathXaml = Path.ChangeExtension(filePath, ".xaml"); - if (File.Exists(filePathXaml)) + var extension = Path.GetExtension(filePath); + var filePathWithoutExtension = filePath.Substring(0, filePath.Length - extension.Length); + if (fileName.EndsWith(".xaml", StringComparison.InvariantCultureIgnoreCase) && File.Exists(filePathWithoutExtension)) + { + fileName = Path.GetFileNameWithoutExtension(fileName); + sourceBuilder.AppendLine($" SharedCodeEnum.{fileName}Cs => \"{fileName}.xaml.cs\","); + sourceBuilder.AppendLine($" SharedCodeEnum.{fileName}Xaml => \"{fileName}.xaml\","); + } + else if (File.Exists(filePathXaml)) { - sourceBuilder.AppendLine($" SharedCodeEnum.{Path.GetFileNameWithoutExtension(filePath)}Xaml => \"{Path.GetFileName(filePathXaml)}\","); - sourceBuilder.AppendLine($" SharedCodeEnum.{Path.GetFileNameWithoutExtension(filePath)}Cs => \"{Path.GetFileName(filePath)}\","); + sourceBuilder.AppendLine($" SharedCodeEnum.{fileName}Xaml => \"{Path.GetFileName(filePathXaml)}\","); + sourceBuilder.AppendLine($" SharedCodeEnum.{fileName}Cs => \"{Path.GetFileName(filePath)}\","); } else { - sourceBuilder.AppendLine($" SharedCodeEnum.{Path.GetFileNameWithoutExtension(filePath)} => \"{Path.GetFileName(filePath)}\","); + sourceBuilder.AppendLine($" SharedCodeEnum.{fileName} => \"{Path.GetFileName(filePath)}\","); } } @@ -111,6 +127,17 @@ private void ExecuteSharedCodeEnumGeneration(SourceProductionContext context, Im { var fileName = Path.GetFileNameWithoutExtension(filePath); var filePathXaml = Path.ChangeExtension(filePath, ".xaml"); + var extension = Path.GetExtension(filePath); + var filePathWithoutExtension = filePath.Substring(0, filePath.Length - extension.Length); + + // handle .xaml.cs files + if (File.Exists(filePathWithoutExtension)) + { + filePathXaml = filePathWithoutExtension; + fileName = Path.GetFileNameWithoutExtension(fileName); + } + + if (File.Exists(filePathXaml)) { var fileContentXaml = XamlSourceCleanUp(File.ReadAllText(filePathXaml)); diff --git a/AIDevGallery/Controls/ModelSelectionControl.xaml b/AIDevGallery/Controls/ModelSelectionControl.xaml index 6c9b313c..c12e260c 100644 --- a/AIDevGallery/Controls/ModelSelectionControl.xaml +++ b/AIDevGallery/Controls/ModelSelectionControl.xaml @@ -91,7 +91,7 @@ ToolTipService.ToolTip="More info"> - + @@ -170,11 +170,19 @@ Click="ModelCard_Click" Icon="{ui:FontIcon Glyph=}" Tag="{x:Bind ModelDetails}" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" Text="View model card" /> + @@ -265,7 +273,7 @@ ToolTipService.ToolTip="More info"> - + @@ -434,7 +442,7 @@ ToolTipService.ToolTip="More info"> - + @@ -528,11 +536,19 @@ Click="ModelCard_Click" Icon="{ui:FontIcon Glyph=}" Tag="{x:Bind ModelDetails}" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" Text="View model card" /> + diff --git a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs index d7be0b92..10cc964d 100644 --- a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs +++ b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs @@ -240,13 +240,22 @@ private void PopulateModelDetailsLists() if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.Compatible) { - AvailableModels.Add(new AvailableModel(modelDetails)); + if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.WCRAPI)) + { + // insert APIs on top + AvailableModels.Insert(0, new AvailableModel(modelDetails)); + } + else + { + AvailableModels.Add(new AvailableModel(modelDetails)); + } } else { if (model.Size == 0) { - UnavailableModels.Add(new BaseModel(modelDetails)); + // insert APIs on top + UnavailableModels.Insert(0, new BaseModel(modelDetails)); } else { @@ -352,6 +361,18 @@ private void ModelCard_Click(object sender, RoutedEventArgs e) } } + private void ApiDocumentation_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem btn && btn.Tag is ModelDetails details) + { + // we are in the sample view, open in app modelcard + if (ModelCardVisibility == Visibility.Visible) + { + App.MainWindow.Navigate("apis", details); + } + } + } + private void CopyModelPath_Click(object sender, RoutedEventArgs e) { if (sender is MenuFlyoutItem btn && btn.Tag is ModelDetails details) diff --git a/AIDevGallery/Helpers/ModelDetailsHelper.cs b/AIDevGallery/Helpers/ModelDetailsHelper.cs index f5a63fa8..1220b657 100644 --- a/AIDevGallery/Helpers/ModelDetailsHelper.cs +++ b/AIDevGallery/Helpers/ModelDetailsHelper.cs @@ -34,7 +34,7 @@ public static ModelDetails GetModelDetailsFromApiDefinition(ModelType modelType, Id = apiDefinition.Id, Icon = apiDefinition.Icon, Name = apiDefinition.Name, - HardwareAccelerators = [HardwareAccelerator.QNN], + HardwareAccelerators = [HardwareAccelerator.WCRAPI], IsUserAdded = false, SupportedOnQualcomm = true, ReadmeUrl = apiDefinition.ReadmeUrl, diff --git a/AIDevGallery/MainWindow.xaml.cs b/AIDevGallery/MainWindow.xaml.cs index e673c359..76e6f4b0 100644 --- a/AIDevGallery/MainWindow.xaml.cs +++ b/AIDevGallery/MainWindow.xaml.cs @@ -97,7 +97,7 @@ private void Navigate(Type page, object? param = null) if (page == typeof(APISelectionPage) && NavFrame.Content is APISelectionPage apiPage && param != null) { // No need to navigate to the APISelectionPage again, we just want to navigate to the right subpage - apiPage.SetSelectedAPIInMenu((ModelType)param); + apiPage.SetSelectedApiInMenu((ModelType)param); } else { diff --git a/AIDevGallery/Models/ModelCompatibility.cs b/AIDevGallery/Models/ModelCompatibility.cs index 3705740d..bbc783c9 100644 --- a/AIDevGallery/Models/ModelCompatibility.cs +++ b/AIDevGallery/Models/ModelCompatibility.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using AIDevGallery.Samples; using AIDevGallery.Utils; using System; +using System.Linq; namespace AIDevGallery.Models; @@ -20,9 +22,23 @@ private ModelCompatibility() public static ModelCompatibility GetModelCompatibility(ModelDetails modelDetails) { string description = string.Empty; - ModelCompatibilityState compatibility; - if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.CPU) || + + // check if WCR API + if (ModelTypeHelpers.ApiDefinitionDetails.Any(md => md.Value.Id == modelDetails.Id)) + { + var apiType = ModelTypeHelpers.ApiDefinitionDetails.FirstOrDefault(md => md.Value.Id == modelDetails.Id).Key; + if (WcrCompatibilityChecker.GetApiAvailability(apiType) != WcrApiAvailability.NotSupported) + { + compatibility = ModelCompatibilityState.Compatible; + } + else + { + compatibility = ModelCompatibilityState.NotCompatible; + description = "This Windows Copilot Runtime API requires a Copilot+ PC and a Windows 11 Insider Preview Build 26120.3073 (Dev and Beta Channels)."; + } + } + else if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.CPU) || (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.QNN) && DeviceUtils.IsArm64())) { compatibility = ModelCompatibilityState.Compatible; diff --git a/AIDevGallery/Models/Samples.cs b/AIDevGallery/Models/Samples.cs index 57b16079..e6f5d6cd 100644 --- a/AIDevGallery/Models/Samples.cs +++ b/AIDevGallery/Models/Samples.cs @@ -156,7 +156,8 @@ internal enum HardwareAccelerator { CPU, DML, - QNN + QNN, + WCRAPI } #pragma warning restore SA1402 // File may only contain a single type diff --git a/AIDevGallery/Pages/APISelectionPage.xaml.cs b/AIDevGallery/Pages/APISelectionPage.xaml.cs index 07d18f48..c494045b 100644 --- a/AIDevGallery/Pages/APISelectionPage.xaml.cs +++ b/AIDevGallery/Pages/APISelectionPage.xaml.cs @@ -6,7 +6,9 @@ using AIDevGallery.Telemetry.Events; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; +using System; using System.Collections.Generic; +using System.Linq; namespace AIDevGallery.Pages; @@ -25,7 +27,13 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter is ModelType type) { - SetSelectedAPIInMenu(type); + SetSelectedApiInMenu(type); + } + else if (e.Parameter is ModelDetails details && + ModelTypeHelpers.ApiDefinitionDetails.Any(md => md.Value.Id == details.Id)) + { + var apiType = ModelTypeHelpers.ApiDefinitionDetails.FirstOrDefault(md => md.Value.Id == details.Id).Key; + SetSelectedApiInMenu(apiType); } else { @@ -63,7 +71,7 @@ private void NavView_SelectionChanged(NavigationView sender, NavigationViewSelec } } - public void SetSelectedAPIInMenu(ModelType selectedType) + public void SetSelectedApiInMenu(ModelType selectedType) { foreach (var item in NavView.MenuItems) { diff --git a/AIDevGallery/Pages/ScenarioSelectionPage.xaml.cs b/AIDevGallery/Pages/ScenarioSelectionPage.xaml.cs index 88639965..de8ef348 100644 --- a/AIDevGallery/Pages/ScenarioSelectionPage.xaml.cs +++ b/AIDevGallery/Pages/ScenarioSelectionPage.xaml.cs @@ -21,8 +21,7 @@ internal record FilterRecord(string? Tag, string Text); new(null, "All" ), new("npu", "NPU" ), new("gpu", "GPU" ), - - // new("wcr-api", "WCR API" ) + new("wcr-api", "WCR API" ) ]; private static LastInternalNavigation? lastInternalNavigation; diff --git a/AIDevGallery/ProjectGenerator/Generator.cs b/AIDevGallery/ProjectGenerator/Generator.cs index 0590f165..26f590af 100644 --- a/AIDevGallery/ProjectGenerator/Generator.cs +++ b/AIDevGallery/ProjectGenerator/Generator.cs @@ -674,10 +674,16 @@ private string CleanXamlSource(string xamlCode, string newNamespace, out string if (match.Success) { var oldClassFullName = match.Groups[1].Value; - _ = oldClassFullName[..oldClassFullName.LastIndexOf('.')]; className = oldClassFullName[(oldClassFullName.LastIndexOf('.') + 1)..]; - xamlCode = xamlCode.Replace(match.Value, @$"x:Class=""{newNamespace}.Sample"""); + if (oldClassFullName.Contains(".SharedCode.")) + { + xamlCode = xamlCode.Replace(match.Value, @$"x:Class=""{newNamespace}.{className}"""); + } + else + { + xamlCode = xamlCode.Replace(match.Value, @$"x:Class=""{newNamespace}.Sample"""); + } } else { diff --git a/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs b/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs index e6a08d3c..2b64b73e 100644 --- a/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs +++ b/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs @@ -4,5 +4,6 @@ internal enum HardwareAccelerator { CPU, DML, - QNN + QNN, + WCRAPI } \ No newline at end of file diff --git a/AIDevGallery/Samples/SharedCode/PhiSilicaClient.cs b/AIDevGallery/Samples/SharedCode/WcrApis/PhiSilicaClient.cs similarity index 100% rename from AIDevGallery/Samples/SharedCode/PhiSilicaClient.cs rename to AIDevGallery/Samples/SharedCode/WcrApis/PhiSilicaClient.cs diff --git a/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml b/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml new file mode 100644 index 00000000..d6ff2c4f --- /dev/null +++ b/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml @@ -0,0 +1,67 @@ + + + + + + + This Windows Copilot Runtime API requires a model download. Click "Make Available" to request the model download via Windows Update. + + + + + + + + + Windows Update + + + + + + + + + + + + diff --git a/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml.cs b/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml.cs new file mode 100644 index 00000000..f8fce323 --- /dev/null +++ b/AIDevGallery/Samples/SharedCode/WcrApis/WcrModelDownloader.xaml.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.Windows.Management.Deployment; +using System; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.System; + +namespace AIDevGallery.Samples.SharedCode; +internal sealed partial class WcrModelDownloader : UserControl +{ + public event EventHandler? DownloadClicked; + + public int DownloadProgress + { + get { return (int)GetValue(DownloadProgressProperty); } + set { SetValue(DownloadProgressProperty, value); } + } + + // Using a DependencyProperty as the backing store for DownloadProgress. This enables animation, styling, binding, etc... + public static readonly DependencyProperty DownloadProgressProperty = + DependencyProperty.Register("DownloadProgress", typeof(int), typeof(WcrModelDownloader), new PropertyMetadata(0)); + + public string ErrorMessage + { + get { return (string)GetValue(ErrorMessageProperty); } + set { SetValue(ErrorMessageProperty, value); } + } + + // Using a DependencyProperty as the backing store for ErrorMessage. This enables animation, styling, binding, etc... + public static readonly DependencyProperty ErrorMessageProperty = + DependencyProperty.Register("ErrorMessage", typeof(string), typeof(WcrModelDownloader), new PropertyMetadata("Error downloading model")); + + public WcrApiDownloadState State + { + get { return (WcrApiDownloadState)GetValue(StateProperty); } + set { SetValue(StateProperty, value); } + } + + // Using a DependencyProperty as the backing store for State. This enables animation, styling, binding, etc... + public static readonly DependencyProperty StateProperty = + DependencyProperty.Register("State", typeof(WcrApiDownloadState), typeof(WcrModelDownloader), new PropertyMetadata(WcrApiDownloadState.NotStarted, OnStateChanged)); + + private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((WcrModelDownloader)d).UpdateState((WcrApiDownloadState)e.NewValue); + } + + private void UpdateState(WcrApiDownloadState state) + { + switch (state) + { + case WcrApiDownloadState.NotStarted: + NotDownloadedContent.Visibility = Visibility.Visible; + loadingRingContainer.Visibility = Visibility.Collapsed; + errorContent.Visibility = Visibility.Collapsed; + this.Visibility = Visibility.Visible; + break; + case WcrApiDownloadState.Downloading: + NotDownloadedContent.Visibility = Visibility.Collapsed; + loadingRingContainer.Visibility = Visibility.Visible; + errorContent.Visibility = Visibility.Collapsed; + this.Visibility = Visibility.Visible; + break; + case WcrApiDownloadState.Downloaded: + NotDownloadedContent.Visibility = Visibility.Collapsed; + loadingRingContainer.Visibility = Visibility.Collapsed; + errorContent.Visibility = Visibility.Collapsed; + this.Visibility = Visibility.Collapsed; + break; + case WcrApiDownloadState.Error: + NotDownloadedContent.Visibility = Visibility.Collapsed; + loadingRingContainer.Visibility = Visibility.Collapsed; + errorContent.Visibility = Visibility.Visible; + this.Visibility = Visibility.Visible; + break; + default: + break; + } + } + + public WcrModelDownloader() + { + this.InitializeComponent(); + } + + public async Task SetDownloadOperation(IAsyncOperationWithProgress operation) + { + if (operation == null) + { + return false; + } + + operation.Progress = (result, progress) => + { + DispatcherQueue.TryEnqueue(() => + { + DownloadProgress = (int)(progress.Progress * 100); + }); + }; + + State = WcrApiDownloadState.Downloading; + + try + { + var result = await operation; + + if (result.Status == PackageDeploymentStatus.CompletedSuccess) + { + State = WcrApiDownloadState.Downloaded; + return true; + } + else + { + State = WcrApiDownloadState.Error; + ErrorMessage = result.ExtendedError.Message; + } + } + catch (Exception ex) + { + ErrorMessage = ex.Message; + State = WcrApiDownloadState.Error; + } + + return false; + } + + private void DownloadModelClicked(object sender, RoutedEventArgs e) + { + DownloadClicked?.Invoke(this, EventArgs.Empty); + } + + private async void WindowsUpdateHyperlinkClicked(Microsoft.UI.Xaml.Documents.Hyperlink sender, Microsoft.UI.Xaml.Documents.HyperlinkClickEventArgs args) + { + var uri = new Uri("ms-settings:windowsupdate"); + await Launcher.LaunchUriAsync(uri); + } +} + +internal enum WcrApiDownloadState +{ + NotStarted, + Downloading, + Downloaded, + Error +} \ No newline at end of file diff --git a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml index e125d4f9..8427c4d5 100644 --- a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml +++ b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml @@ -3,11 +3,11 @@ x:Class="AIDevGallery.Samples.WCRAPIs.BackgroundRemover" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:samples="using:AIDevGallery.Samples" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> @@ -99,5 +99,10 @@ VerticalAlignment="Center" IsActive="True" Visibility="Collapsed" /> + diff --git a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs index 7c505283..ab21d9f7 100644 --- a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs +++ b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs @@ -3,6 +3,7 @@ using AIDevGallery.Models; using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; using Microsoft.Graphics.Imaging; using Microsoft.UI; using Microsoft.UI.Xaml; @@ -11,7 +12,6 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Shapes; -using Microsoft.Windows.Management.Deployment; using System; using System.Collections.Generic; using System.Linq; @@ -31,6 +31,7 @@ namespace AIDevGallery.Samples.WCRAPIs; Model1Types = [ModelType.BackgroundRemover], Scenario = ScenarioType.ImageBackgroundRemover, Id = "79eca6f0-3092-4b6f-9a81-94a2aff22559", + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], Icon = "\uEE6F")] internal sealed partial class BackgroundRemover : BaseSamplePage { @@ -44,19 +45,21 @@ public BackgroundRemover() protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) { - if (!ImageObjectExtractor.IsAvailable()) + if (ImageObjectExtractor.IsAvailable()) { - sampleParams.ShowWcrModelLoadingMessage = true; - var loadResult = await ImageObjectExtractor.MakeAvailableAsync(); - if (loadResult.Status != PackageDeploymentStatus.CompletedSuccess) - { - throw new InvalidOperationException(loadResult.ExtendedError.Message); - } + WcrModelDownloader.State = WcrApiDownloadState.Downloaded; } sampleParams.NotifyCompletion(); } + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = ImageObjectExtractor.MakeAvailableAsync(); + + await WcrModelDownloader.SetDownloadOperation(operation); + } + private async void LoadImage_Click(object sender, RoutedEventArgs e) { var window = new Window(); diff --git a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml index 1eefc2dc..02291c28 100644 --- a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml +++ b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml @@ -3,10 +3,9 @@ x:Class="AIDevGallery.Samples.WCRAPIs.ImageDescription" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:AIDevGallery.Controls" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:samples="using:AIDevGallery.Samples" mc:Ignorable="d"> @@ -57,5 +56,10 @@ + diff --git a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs index 294fd379..db90c346 100644 --- a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs +++ b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs @@ -3,11 +3,11 @@ using AIDevGallery.Models; using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; using Microsoft.Graphics.Imaging; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.Windows.AI.Generative; -using Microsoft.Windows.Management.Deployment; using System; using System.IO; using System.Linq; @@ -25,6 +25,7 @@ namespace AIDevGallery.Samples.WCRAPIs; Model1Types = [ModelType.ImageDescription], Scenario = ScenarioType.ImageDescribeImageWcr, Id = "a1b1f64f-bc57-41a3-8fb3-ac8f1536d757", + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], Icon = "\uEE6F")] internal sealed partial class ImageDescription : BaseSamplePage @@ -39,19 +40,21 @@ public ImageDescription() protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) { sampleParams.ShowWcrModelLoadingMessage = true; - if (!ImageDescriptionGenerator.IsAvailable()) + if (ImageDescriptionGenerator.IsAvailable()) { - var loadResult = await ImageDescriptionGenerator.MakeAvailableAsync(); - if (loadResult.Status != PackageDeploymentStatus.CompletedSuccess) - { - throw new InvalidOperationException(loadResult.ExtendedError.Message); - } + WcrModelDownloader.State = WcrApiDownloadState.Downloaded; } - _imageDescriptor = await ImageDescriptionGenerator.CreateAsync(); sampleParams.NotifyCompletion(); } + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = ImageDescriptionGenerator.MakeAvailableAsync(); + + await WcrModelDownloader.SetDownloadOperation(operation); + } + private async void LoadImage_Click(object sender, RoutedEventArgs e) { var window = new Window(); @@ -142,7 +145,8 @@ private async void DescribeImage(SoftwareBitmap bitmap) try { using var bitmapBuffer = ImageBuffer.CreateCopyFromBitmap(bitmap); - var describeTask = _imageDescriptor?.DescribeAsync(bitmapBuffer); + _imageDescriptor ??= await ImageDescriptionGenerator.CreateAsync(); + var describeTask = _imageDescriptor.DescribeAsync(bitmapBuffer); if (describeTask != null) { describeTask.Progress += (asyncInfo, delta) => diff --git a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml index e7c68f8f..1b7760cb 100644 --- a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml +++ b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml @@ -3,16 +3,16 @@ x:Class="AIDevGallery.Samples.WCRAPIs.IncreaseFidelity" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:samples="using:AIDevGallery.Samples" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> - + @@ -114,5 +114,10 @@ VerticalAlignment="Center" IsActive="True" Visibility="Collapsed" /> + diff --git a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs index c71727e3..e3c75b83 100644 --- a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs +++ b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs @@ -3,6 +3,7 @@ using AIDevGallery.Models; using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; using Microsoft.Graphics.Imaging; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -25,6 +26,7 @@ namespace AIDevGallery.Samples.WCRAPIs; Model1Types = [ModelType.ImageScaler], Scenario = ScenarioType.ImageIncreaseFidelity, Id = "f1e235d1-f1c9-41c7-b489-7e4f95e54668", + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], Icon = "\uEE6F")] internal sealed partial class IncreaseFidelity : BaseSamplePage { @@ -37,26 +39,21 @@ public IncreaseFidelity() protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) { - if (!ImageScaler.IsAvailable()) + if (ImageScaler.IsAvailable()) { - sampleParams.ShowWcrModelLoadingMessage = true; - var loadResult = await ImageScaler.MakeAvailableAsync(); - if (loadResult.Status != PackageDeploymentStatus.CompletedSuccess) - { - throw new InvalidOperationException(loadResult.ExtendedError.Message); - } - } - - _imageScaler = await ImageScaler.CreateAsync(); - ScaleSlider.Maximum = _imageScaler.MaxSupportedScaleFactor; - if (_imageScaler.MaxSupportedScaleFactor >= 2) - { - ScaleSlider.Value = 2; + WcrModelDownloader.State = WcrApiDownloadState.Downloaded; } sampleParams.NotifyCompletion(); } + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = ImageScaler.MakeAvailableAsync(); + + await WcrModelDownloader.SetDownloadOperation(operation); + } + private async void LoadImage_Click(object sender, RoutedEventArgs e) { var window = new Window(); @@ -131,23 +128,35 @@ private async Task SetImage(IRandomAccessStream stream) private async void ScaleImage() { - if (_imageScaler != null && _originalImage != null) + if (_originalImage == null) { - ScaledPanel.Visibility = Visibility.Collapsed; - Loader.Visibility = Visibility.Visible; - - var newWidth = (int)(_originalImage.PixelWidth * ScaleSlider.Value); - var newHeight = (int)(_originalImage.PixelHeight * ScaleSlider.Value); + return; + } - var bitmap = await Task.Run(() => + if (_imageScaler == null) + { + _imageScaler = await ImageScaler.CreateAsync(); + ScaleSlider.Maximum = _imageScaler.MaxSupportedScaleFactor; + if (_imageScaler.MaxSupportedScaleFactor >= 2) { - return _imageScaler.ScaleSoftwareBitmap(_originalImage, newWidth, newHeight); - }); - - Loader.Visibility = Visibility.Collapsed; - ScaledPanel.Visibility = Visibility.Visible; - await SetImageSource(ScaledImage, bitmap, ScaledDimensionsTxt); + ScaleSlider.Value = 2; + } } + + ScaledPanel.Visibility = Visibility.Collapsed; + Loader.Visibility = Visibility.Visible; + + var newWidth = (int)(_originalImage.PixelWidth * ScaleSlider.Value); + var newHeight = (int)(_originalImage.PixelHeight * ScaleSlider.Value); + + var bitmap = await Task.Run(() => + { + return _imageScaler.ScaleSoftwareBitmap(_originalImage, newWidth, newHeight); + }); + + Loader.Visibility = Visibility.Collapsed; + ScaledPanel.Visibility = Visibility.Visible; + await SetImageSource(ScaledImage, bitmap, ScaledDimensionsTxt); } private async Task SetImageSource(Image image, SoftwareBitmap softwareBitmap, Run textBlock) diff --git a/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml b/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml index a7776ff0..34e6bd4d 100644 --- a/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml +++ b/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml @@ -3,11 +3,11 @@ x:Class="AIDevGallery.Samples.WCRAPIs.OCRLineSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:samples="using:AIDevGallery.Samples" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> @@ -79,5 +79,10 @@ VerticalAlignment="Center" IsActive="True" Visibility="Collapsed" /> + diff --git a/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml.cs b/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml.cs index 720783f9..e658f053 100644 --- a/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml.cs +++ b/AIDevGallery/Samples/WCRAPIs/OCRLineSample.xaml.cs @@ -3,6 +3,7 @@ using AIDevGallery.Models; using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; using Microsoft.Graphics.Imaging; using Microsoft.UI; using Microsoft.UI.Xaml; @@ -10,7 +11,6 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Shapes; -using Microsoft.Windows.Management.Deployment; using Microsoft.Windows.Vision; using System; using System.Linq; @@ -32,6 +32,7 @@ namespace AIDevGallery.Samples.WCRAPIs; Model1Types = [ModelType.TextRecognitionOCR], Scenario = ScenarioType.ImageDetectTextLines, Id = "e26ef7bc-d847-4b2e-862a-74d872bb8635", + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], Icon = "\uEE6F")] internal sealed partial class OCRLineSample : BaseSamplePage { @@ -44,21 +45,21 @@ public OCRLineSample() protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) { - if (!TextRecognizer.IsAvailable()) + if (TextRecognizer.IsAvailable()) { - sampleParams.ShowWcrModelLoadingMessage = true; - var loadResult = await TextRecognizer.MakeAvailableAsync(); - if (loadResult.Status != PackageDeploymentStatus.CompletedSuccess) - { - throw new InvalidOperationException(loadResult.ExtendedError.Message); - } + WcrModelDownloader.State = WcrApiDownloadState.Downloaded; } - _textRecognizer = await TextRecognizer.CreateAsync(); - sampleParams.NotifyCompletion(); } + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = TextRecognizer.MakeAvailableAsync(); + + await WcrModelDownloader.SetDownloadOperation(operation); + } + private async void LoadImage_Click(object sender, RoutedEventArgs e) { var window = new Window(); @@ -147,6 +148,7 @@ private async Task RecognizeAndAddTextAsync(SoftwareBitmap bitmap) Loader.Visibility = Visibility.Visible; RectCanvas.Visibility = Visibility.Collapsed; using var imageBuffer = ImageBuffer.CreateBufferAttachedToBitmap(bitmap); + _textRecognizer ??= await TextRecognizer.CreateAsync(); RecognizedText? result = _textRecognizer?.RecognizeTextFromImage(imageBuffer, new TextRecognizerOptions()); if (result == null) { diff --git a/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml b/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml index 43c30488..d6dba7b3 100644 --- a/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml +++ b/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml @@ -3,11 +3,11 @@ x:Class="AIDevGallery.Samples.WCRAPIs.OCRSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:samples="using:AIDevGallery.Samples" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> @@ -80,5 +80,10 @@ VerticalAlignment="Center" IsActive="True" Visibility="Collapsed" /> + diff --git a/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml.cs b/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml.cs index 264fcae3..e9a6db01 100644 --- a/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml.cs +++ b/AIDevGallery/Samples/WCRAPIs/OCRSample.xaml.cs @@ -3,6 +3,7 @@ using AIDevGallery.Models; using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; using Microsoft.Graphics.Imaging; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Documents; @@ -26,6 +27,7 @@ namespace AIDevGallery.Samples.WCRAPIs; Model1Types = [ModelType.TextRecognitionOCR], Scenario = ScenarioType.ImageDetectText, Id = "8f072b64-74fc-4511-b84f-e09d56394f07", + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], Icon = "\uEE6F")] internal sealed partial class OCRSample : BaseSamplePage { @@ -38,21 +40,22 @@ public OCRSample() protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) { - if (!TextRecognizer.IsAvailable()) + if (TextRecognizer.IsAvailable()) { - sampleParams.ShowWcrModelLoadingMessage = true; - var loadResult = await TextRecognizer.MakeAvailableAsync(); - if (loadResult.Status != PackageDeploymentStatus.CompletedSuccess) - { - throw new InvalidOperationException(loadResult.ExtendedError.Message); - } - } + WcrModelDownloader.State = WcrApiDownloadState.Downloaded; - _textRecognizer = await TextRecognizer.CreateAsync(); + } sampleParams.NotifyCompletion(); } + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = TextRecognizer.MakeAvailableAsync(); + + await WcrModelDownloader.SetDownloadOperation(operation); + } + private async void LoadImage_Click(object sender, RoutedEventArgs e) { var window = new Window(); @@ -132,10 +135,7 @@ private async Task SetImage(IRandomAccessStream stream) public async Task RecognizeAndAddTextAsync(SoftwareBitmap bitmap) { - if (_textRecognizer == null) - { - return; - } + _textRecognizer ??= await TextRecognizer.CreateAsync(); OutputPanel.Visibility = Visibility.Collapsed; Loader.Visibility = Visibility.Visible; diff --git a/AIDevGallery/Utils/AppUtils.cs b/AIDevGallery/Utils/AppUtils.cs index c7931012..8ba06079 100644 --- a/AIDevGallery/Utils/AppUtils.cs +++ b/AIDevGallery/Utils/AppUtils.cs @@ -85,6 +85,8 @@ public static string GetHardwareAcceleratorString(HardwareAccelerator hardwareAc return "GPU"; case HardwareAccelerator.QNN: return "NPU"; + case HardwareAccelerator.WCRAPI: + return "WCR"; default: return hardwareAccelerator.ToString(); } @@ -101,6 +103,8 @@ public static string GetHardwareAcceleratorDescription(HardwareAccelerator hardw return "This model will run on supported GPUs with DirectML"; case HardwareAccelerator.QNN: return "This model will run on Qualcomm NPUs"; + case HardwareAccelerator.WCRAPI: + return "The model used by this Windows Copilot Runtime API will run on NPU"; } } diff --git a/AIDevGallery/Utils/WcrCompatibilityChecker.cs b/AIDevGallery/Utils/WcrCompatibilityChecker.cs new file mode 100644 index 00000000..da30ccf7 --- /dev/null +++ b/AIDevGallery/Utils/WcrCompatibilityChecker.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using Microsoft.Graphics.Imaging; +using Microsoft.Windows.AI.Generative; +using Microsoft.Windows.Vision; +using System; +using System.Collections.Generic; + +namespace AIDevGallery.Utils; +internal static class WcrCompatibilityChecker +{ + private static readonly Dictionary> CompatibilityCheckers = new Dictionary> + { + { + ModelType.PhiSilica, LanguageModel.IsAvailable + }, + { + ModelType.TextRecognitionOCR, TextRecognizer.IsAvailable + }, + { + ModelType.ImageScaler, ImageScaler.IsAvailable + }, + { + ModelType.BackgroundRemover, ImageObjectExtractor.IsAvailable + }, + { + ModelType.ImageDescription, ImageDescriptionGenerator.IsAvailable + } + }; + + public static WcrApiAvailability GetApiAvailability(ModelType type) + { + if (!CompatibilityCheckers.TryGetValue(type, out Func? isAvailable)) + { + return WcrApiAvailability.NotSupported; + } + + try + { + return isAvailable() ? WcrApiAvailability.Available : WcrApiAvailability.NotAvailable; + } + catch + { + return WcrApiAvailability.NotSupported; + } + } +} + +internal enum WcrApiAvailability +{ + Available, + NotAvailable, + NotSupported +} \ No newline at end of file