diff --git a/AIDevGallery.SourceGenerator/Models/ApiDefinition.cs b/AIDevGallery.SourceGenerator/Models/ApiDefinition.cs index bf0e9039..9616cbfc 100644 --- a/AIDevGallery.SourceGenerator/Models/ApiDefinition.cs +++ b/AIDevGallery.SourceGenerator/Models/ApiDefinition.cs @@ -8,6 +8,9 @@ internal class ApiDefinition public required string Id { get; init; } public required string Name { get; init; } public required string Icon { get; init; } + public required string IconGlyph { get; init; } + public required string Description { get; init; } public required string ReadmeUrl { get; init; } public required string License { get; init; } + public required string SampleIdToShowInDocs { get; init; } } \ No newline at end of file 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/ModelsSourceGenerator.cs b/AIDevGallery.SourceGenerator/ModelsSourceGenerator.cs index d21a1855..95ac4f1e 100644 --- a/AIDevGallery.SourceGenerator/ModelsSourceGenerator.cs +++ b/AIDevGallery.SourceGenerator/ModelsSourceGenerator.cs @@ -24,8 +24,7 @@ internal class ModelSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider modelJsons = context.AdditionalTextsProvider.Where( - static file => file.Path.EndsWith(".json") && - Path.GetFileName(Path.GetDirectoryName(file.Path)).Equals("ModelsDefinitions", StringComparison.OrdinalIgnoreCase)); + static file => file.Path.EndsWith(".json") && file.Path.Contains(@"\Samples\Definitions\")); var pathsAndContents = modelJsons.Select((text, cancellationToken) => (text.Path, Content: text.GetText(cancellationToken)!.ToString(), CancellationToken: cancellationToken)) @@ -322,8 +321,11 @@ private void GenerateApiDefinitionDetails(StringBuilder sourceBuilder, Dictionar Id = "{{apiDefinition.Id}}", Name = "{{apiDefinition.Name}}", Icon = "{{apiDefinition.Icon}}", + IconGlyph = "{{apiDefinition.IconGlyph}}", + Description = "{{apiDefinition.Description}}", ReadmeUrl = "{{apiDefinition.ReadmeUrl}}", License = "{{apiDefinition.License}}", + {{(!string.IsNullOrWhiteSpace(apiDefinition.SampleIdToShowInDocs) ? $"SampleIdToShowInDocs = \"{apiDefinition.SampleIdToShowInDocs}\"" : string.Empty)}} } }, """"); diff --git a/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs b/AIDevGallery.SourceGenerator/SamplesSourceGenerator.cs index dd319abe..ec0f488b 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)) { - sourceBuilder.AppendLine($" SharedCodeEnum.{Path.GetFileNameWithoutExtension(filePath)}Xaml => \"{Path.GetFileName(filePathXaml)}\","); - sourceBuilder.AppendLine($" SharedCodeEnum.{Path.GetFileNameWithoutExtension(filePath)}Cs => \"{Path.GetFileName(filePath)}\","); + 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.{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,16 @@ 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.UnitTests/ProjectGeneratorUnitTests.cs b/AIDevGallery.UnitTests/ProjectGeneratorUnitTests.cs index a12923c6..fb0cf3e2 100644 --- a/AIDevGallery.UnitTests/ProjectGeneratorUnitTests.cs +++ b/AIDevGallery.UnitTests/ProjectGeneratorUnitTests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -62,7 +63,9 @@ private class SampleUIData : INotifyPropertyChanged { private Brush? statusColor; + public required string SampleName { get; init; } public required Sample Sample { get; init; } + public required Dictionary CachedModelsToGenerator { get; init; } public Brush? StatusColor { get => statusColor; @@ -93,11 +96,7 @@ public async Task GenerateForAllSamples() UITestMethodAttribute.DispatcherQueue?.TryEnqueue(() => { - source = SampleDetails.Samples.Select(s => new SampleUIData - { - Sample = s, - StatusColor = new SolidColorBrush(Colors.LightGray) - }).ToList(); + source = SampleDetails.Samples.SelectMany(s => GetAllForSample(s)).ToList(); green = new SolidColorBrush(Colors.Green); red = new SolidColorBrush(Colors.Red); @@ -119,7 +118,7 @@ public async Task GenerateForAllSamples() // write test count TestContext.WriteLine($"Running {source.Count} tests"); - + int currentId = 0; await Parallel.ForEachAsync(source, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async (item, ct) => { listView.DispatcherQueue.TryEnqueue(() => @@ -127,41 +126,81 @@ public async Task GenerateForAllSamples() item.StatusColor = yellow; }); - var success = await GenerateForSample(item.Sample, ct); + Interlocked.Increment(ref currentId); - TestContext.WriteLine($"Built {item.Sample.Name} with status {success}"); - Debug.WriteLine($"Built {item.Sample.Name} with status {success}"); + var success = await GenerateForSample(currentId, item, ct); + + TestContext.WriteLine($"Built {item.SampleName} with status {success}"); + Debug.WriteLine($"Built {item.SampleName} with status {success}"); listView.DispatcherQueue.TryEnqueue(() => { item.StatusColor = success ? green : red; }); - successDict.Add(item.Sample.Name, success); + successDict.Add(item.SampleName, success); }); successDict.Should().AllSatisfy(kvp => kvp.Value.Should().BeTrue($"{kvp.Key} should build successfully")); } - private async Task GenerateForSample(Sample sample, CancellationToken cancellationToken) + private static IEnumerable GetAllForSample(Sample s) { - var modelsDetails = ModelDetailsHelper.GetModelDetails(sample); + var modelsDetails = ModelDetailsHelper.GetModelDetails(s); + + if (modelsDetails[0].ContainsKey(ModelType.LanguageModels) && + modelsDetails[0].ContainsKey(ModelType.PhiSilica)) + { + yield return new SampleUIData + { + Sample = s, + SampleName = $"{s.Name} GenAI", + CachedModelsToGenerator = GetModelsToGenerator(s, modelsDetails, modelsDetails[0].First(md => md.Key == ModelType.LanguageModels)), + StatusColor = new SolidColorBrush(Colors.LightGray) + }; - ModelDetails modelDetails1 = modelsDetails[0].Values.First().First(); - Dictionary cachedModelsToGenerator = new() + yield return new SampleUIData + { + Sample = s, + SampleName = $"{s.Name} PhiSilica", + CachedModelsToGenerator = GetModelsToGenerator(s, modelsDetails, modelsDetails[0].First(md => md.Key == ModelType.PhiSilica)), + StatusColor = new SolidColorBrush(Colors.LightGray) + }; + } + else { - [sample.Model1Types.First()] = ("FakePath", modelsDetails[0].Values.First().First().Url, modelDetails1.HardwareAccelerators.First()) - }; + yield return new SampleUIData + { + Sample = s, + SampleName = s.Name, + CachedModelsToGenerator = GetModelsToGenerator(s, modelsDetails, modelsDetails[0].First()), + StatusColor = new SolidColorBrush(Colors.LightGray) + }; + } - if (sample.Model2Types != null && modelsDetails.Count > 1) + static Dictionary GetModelsToGenerator(Sample s, List>> modelsDetails, KeyValuePair> keyValuePair) { - ModelDetails modelDetails2 = modelsDetails[1].Values.First().First(); - cachedModelsToGenerator[sample.Model2Types.First()] = ("FakePath", modelDetails2.Url, modelDetails2.HardwareAccelerators.First()); + Dictionary cachedModelsToGenerator = new(); + + ModelDetails modelDetails1 = keyValuePair.Value.First(); + cachedModelsToGenerator[keyValuePair.Key] = (modelDetails1.Url, modelDetails1.Url, modelDetails1.HardwareAccelerators.First()); + + if (s.Model2Types != null && modelsDetails.Count > 1) + { + ModelDetails modelDetails2 = modelsDetails[1].Values.First().First(); + cachedModelsToGenerator[s.Model2Types.First()] = (modelDetails2.Url, modelDetails2.Url, modelDetails2.HardwareAccelerators.First()); + } + + return cachedModelsToGenerator; } + } - var projectPath = await generator.GenerateAsync(sample, cachedModelsToGenerator, false, TmpPathProjectGenerator, cancellationToken); + private async Task GenerateForSample(int id, SampleUIData sampleUIData, CancellationToken cancellationToken) + { + var outputPath = Path.Join(TmpPathProjectGenerator, id.ToString(CultureInfo.InvariantCulture)); + var projectPath = await generator.GenerateAsync(sampleUIData.Sample, sampleUIData.CachedModelsToGenerator, false, outputPath, cancellationToken); var safeProjectName = Path.GetFileName(projectPath); - string logFileName = $"build_{safeProjectName}.log"; + string logFileName = $"build_{id}_{sampleUIData.SampleName.Replace(' ', '_')}.log"; var arch = DeviceUtils.IsArm64() ? "arm64" : "x64"; diff --git a/AIDevGallery.UnitTests/UnitTestApp.xaml b/AIDevGallery.UnitTests/UnitTestApp.xaml index 736501d3..8c5c9818 100644 --- a/AIDevGallery.UnitTests/UnitTestApp.xaml +++ b/AIDevGallery.UnitTests/UnitTestApp.xaml @@ -14,7 +14,7 @@ - + diff --git a/AIDevGallery/AIDevGallery.csproj b/AIDevGallery/AIDevGallery.csproj index 4079fc50..dc69ab30 100644 --- a/AIDevGallery/AIDevGallery.csproj +++ b/AIDevGallery/AIDevGallery.csproj @@ -322,4 +322,6 @@ + + diff --git a/AIDevGallery/App.xaml b/AIDevGallery/App.xaml index afea8068..4d55b4f4 100644 --- a/AIDevGallery/App.xaml +++ b/AIDevGallery/App.xaml @@ -3,22 +3,25 @@ x:Class="AIDevGallery.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:AIDevGallery"> + xmlns:local="using:AIDevGallery" + xmlns:models="using:AIDevGallery.Models"> + + - - - + + + diff --git a/AIDevGallery/Assets/Enhance.png b/AIDevGallery/Assets/Enhance.png new file mode 100644 index 00000000..7295af03 Binary files /dev/null and b/AIDevGallery/Assets/Enhance.png differ diff --git a/AIDevGallery/Assets/ModelIcons/WCRAPI.svg b/AIDevGallery/Assets/ModelIcons/WCRAPI.svg index aaf9c96a..c95a34a0 100644 --- a/AIDevGallery/Assets/ModelIcons/WCRAPI.svg +++ b/AIDevGallery/Assets/ModelIcons/WCRAPI.svg @@ -1,39 +1,69 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + - + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + diff --git a/AIDevGallery/Assets/OCR.png b/AIDevGallery/Assets/OCR.png new file mode 100644 index 00000000..68c58859 Binary files /dev/null and b/AIDevGallery/Assets/OCR.png differ diff --git a/AIDevGallery/Assets/Road.png b/AIDevGallery/Assets/Road.png new file mode 100644 index 00000000..4681e2c5 Binary files /dev/null and b/AIDevGallery/Assets/Road.png differ diff --git a/AIDevGallery/Controls/Card.xaml b/AIDevGallery/Controls/Card.xaml new file mode 100644 index 00000000..73263356 --- /dev/null +++ b/AIDevGallery/Controls/Card.xaml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Controls/Card.xaml.cs b/AIDevGallery/Controls/Card.xaml.cs new file mode 100644 index 00000000..7d632c06 --- /dev/null +++ b/AIDevGallery/Controls/Card.xaml.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace AIDevGallery.Controls; + +internal sealed partial class Card : UserControl +{ + public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged)); + + public object TitleContent + { + get => (object)GetValue(TitleContentProperty); + set => SetValue(TitleContentProperty, value); + } + + public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged)); + + public string Title + { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1061:Do not hide base class methods", Justification = "We need to hide the base class method")] + public new object Content + { + get => (object)GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + public static readonly DependencyProperty TitlePaddingProperty = DependencyProperty.Register(nameof(TitlePadding), typeof(Thickness), typeof(Card), new PropertyMetadata(defaultValue: new Thickness(12, 12, 16, 12))); + + public Thickness TitlePadding + { + get => (Thickness)GetValue(TitlePaddingProperty); + set => SetValue(TitlePaddingProperty, value); + } + + public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null)); + + public Visibility DividerVisibility + { + get => (Visibility)GetValue(DividerVisibilityProperty); + set => SetValue(DividerVisibilityProperty, value); + } + + public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged)); + + public IconElement Icon + { + get => (IconElement)GetValue(IconProperty); + set => SetValue(IconProperty, value); + } + + public Card() + { + this.InitializeComponent(); + SetVisualStates(); + } + + private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is Card card) + { + card.SetVisualStates(); + } + } + + private void SetVisualStates() + { + VisualStateManager.GoToState(this, Icon != null ? "IconVisible" : "IconCollapsed", true); + + if (string.IsNullOrEmpty(Title) && Icon == null && TitleContent == null) + { + VisualStateManager.GoToState(this, "TitleGridCollapsed", true); + DividerVisibility = Visibility.Collapsed; + } + else + { + VisualStateManager.GoToState(this, "TitleGridVisible", true); + DividerVisibility = Visibility.Visible; + } + } +} \ No newline at end of file diff --git a/AIDevGallery/Controls/DownloadProgressList.xaml b/AIDevGallery/Controls/DownloadProgressList.xaml index 438ae308..59099826 100644 --- a/AIDevGallery/Controls/DownloadProgressList.xaml +++ b/AIDevGallery/Controls/DownloadProgressList.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI" @@ -133,10 +132,10 @@ VerticalAlignment="Center" AutomationProperties.Name="View model page" Click="GoToModelPageClicked" - Tag="{x:Bind}" Content="{ui:FontIcon Glyph=, FontSize=14}" Style="{StaticResource SubtleButtonStyle}" + Tag="{x:Bind}" ToolTipService.ToolTip="View model page" Visibility="{x:Bind vm:DownloadableModel.VisibleWhenDownloaded(Status), Mode=OneWay}" /> @@ -108,8 +105,8 @@ + VerticalSpacing="2" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}"> @@ -141,9 +138,7 @@ - + @@ -170,34 +165,40 @@ Click="ModelCard_Click" Icon="{ui:FontIcon Glyph=}" Tag="{x:Bind ModelDetails}" - Text="View model card" /> + Text="View model card" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> + - + Text="View license" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> + + Text="Copy as path" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> - + Text="Open containing folder" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> + + Text="Delete" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> @@ -265,7 +266,10 @@ ToolTipService.ToolTip="More info"> - + @@ -434,7 +438,10 @@ ToolTipService.ToolTip="More info"> - + @@ -459,8 +466,8 @@ + VerticalSpacing="2" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}"> - + @@ -529,12 +537,20 @@ Click="ModelCard_Click" Icon="{ui:FontIcon Glyph=}" Tag="{x:Bind ModelDetails}" - Text="View model card" /> + Text="View model card" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> + + Text="View license" + Visibility="{x:Bind ModelDetails.Size, Converter={StaticResource NotZeroToVisibilityConverter}}" /> diff --git a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs index d7be0b92..3a6edfd7 100644 --- a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs +++ b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs @@ -137,7 +137,7 @@ private void SetSelectedModel(ModelDetails? modelDetails) { if (modelDetails != null) { - if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.NotCompatible) + if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.NotCompatible && !modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.WCRAPI)) { if (Selected != null) { @@ -238,21 +238,26 @@ private void PopulateModelDetailsLists() FileFilters = model.FileFilters }; - if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.Compatible) + if (modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.WCRAPI)) { - AvailableModels.Add(new AvailableModel(modelDetails)); - } - else - { - if (model.Size == 0) + if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.NotCompatible) { - UnavailableModels.Add(new BaseModel(modelDetails)); + AvailableModels.Add(new AvailableModel(modelDetails)); } else { - UnavailableModels.Add(new DownloadableModel(modelDetails)); + // insert available APIs on top + AvailableModels.Insert(0, new AvailableModel(modelDetails)); } } + else if (modelDetails.Compatibility.CompatibilityState == ModelCompatibilityState.Compatible) + { + AvailableModels.Add(new AvailableModel(modelDetails)); + } + else + { + UnavailableModels.Add(new DownloadableModel(modelDetails)); + } } } @@ -352,6 +357,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/Controls/OverviewPageHeader.xaml b/AIDevGallery/Controls/OverviewPageHeader.xaml index 5c812ace..ecb1e8de 100644 --- a/AIDevGallery/Controls/OverviewPageHeader.xaml +++ b/AIDevGallery/Controls/OverviewPageHeader.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" SizeChanged="Control_SizeChanged" mc:Ignorable="d"> diff --git a/AIDevGallery/Controls/SampleContainer.xaml b/AIDevGallery/Controls/SampleContainer.xaml index fc2212c8..81baa835 100644 --- a/AIDevGallery/Controls/SampleContainer.xaml +++ b/AIDevGallery/Controls/SampleContainer.xaml @@ -3,10 +3,9 @@ x:Class="AIDevGallery.Controls.SampleContainer" 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:local="using:AIDevGallery.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:shared="using:AIDevGallery.Samples.SharedCode" xmlns:toolkitcontrols="using:CommunityToolkit.WinUI.Controls" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" @@ -17,14 +16,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HorizontalAlignment="{x:Bind HorizontalAlignment, Mode=OneWay}" + VerticalAlignment="{x:Bind VerticalAlignment, Mode=OneWay}" + Background="{x:Bind Background, Mode=OneWay}" + BorderBrush="{x:Bind BorderBrush, Mode=OneWay}" + BorderThickness="{x:Bind BorderThickness, Mode=OneWay}" + CornerRadius="{x:Bind CornerRadius, Mode=OneWay}"> @@ -36,34 +82,32 @@ - - - + + + + + + + + + + + + Learn more + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AIDevGallery/Controls/SampleContainer.xaml.cs b/AIDevGallery/Controls/SampleContainer.xaml.cs index 48196f69..69899cae 100644 --- a/AIDevGallery/Controls/SampleContainer.xaml.cs +++ b/AIDevGallery/Controls/SampleContainer.xaml.cs @@ -9,17 +9,27 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; +using Microsoft.Windows.AI.Generative; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Windows.System; namespace AIDevGallery.Controls; internal sealed partial class SampleContainer : UserControl { + public static readonly DependencyProperty DisclaimerHorizontalAlignmentProperty = DependencyProperty.Register(nameof(DisclaimerHorizontalAlignment), typeof(HorizontalAlignment), typeof(SampleContainer), new PropertyMetadata(defaultValue: HorizontalAlignment.Center)); + + public HorizontalAlignment DisclaimerHorizontalAlignment + { + get => (HorizontalAlignment)GetValue(DisclaimerHorizontalAlignmentProperty); + set => SetValue(DisclaimerHorizontalAlignmentProperty, value); + } + private Sample? _sampleCache; private List? _modelsCache; private CancellationTokenSource? _sampleLoadingCts; @@ -136,6 +146,41 @@ public async Task LoadSampleAsync(Sample? sample, List? models) return; } + // show that models are not compatible with this device + if (models.Any(m => m.HardwareAccelerators.Contains(HardwareAccelerator.WCRAPI) && m.Compatibility.CompatibilityState == ModelCompatibilityState.NotCompatible)) + { + VisualStateManager.GoToState(this, "WcrApiNotCompatible", true); + SampleFrame.Content = null; + return; + } + + // if PhiSilica, only show model loader and reload sample once loaded + if (cachedModelsPaths.Any(m => m == $"file://{ModelType.PhiSilica}")) + { + try + { + if (!LanguageModel.IsAvailable()) + { + modelDownloader.State = WcrApiDownloadState.NotStarted; + modelDownloader.ErrorMessage = string.Empty; + modelDownloader.DownloadProgress = 0; + SampleFrame.Content = null; + + VisualStateManager.GoToState(this, "WcrModelNeedsDownload", true); + if (!await modelDownloader.SetDownloadOperation(ModelType.PhiSilica, sample.Id, LanguageModel.MakeAvailableAsync)) + { + return; + } + } + } + catch + { + VisualStateManager.GoToState(this, "WcrApiNotCompatible", true); + SampleFrame.Content = null; + return; + } + } + // model available VisualStateManager.GoToState(this, "SampleLoading", true); SampleFrame.Content = null; @@ -330,4 +375,27 @@ private async void NuGetPackage_Click(object sender, RoutedEventArgs e) await Windows.System.Launcher.LaunchUriAsync(new Uri("https://www.nuget.org/packages/" + url)); } } + + 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); + } + + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + if (!LanguageModel.IsAvailable()) + { + var op = LanguageModel.MakeAvailableAsync(); + if (await modelDownloader.SetDownloadOperation(op)) + { + // reload sample + _ = this.LoadSampleAsync(_sampleCache, _modelsCache); + } + } + else + { + modelDownloader.State = WcrApiDownloadState.Downloaded; + } + } } \ No newline at end of file diff --git a/AIDevGallery/Controls/Shimmer.xaml b/AIDevGallery/Controls/Shimmer.xaml new file mode 100644 index 00000000..e009874c --- /dev/null +++ b/AIDevGallery/Controls/Shimmer.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Controls/Shimmer.xaml.cs b/AIDevGallery/Controls/Shimmer.xaml.cs new file mode 100644 index 00000000..2ec342b8 --- /dev/null +++ b/AIDevGallery/Controls/Shimmer.xaml.cs @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CommunityToolkit.WinUI; +using CommunityToolkit.WinUI.Animations; +using CommunityToolkit.WinUI.Animations.Expressions; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Hosting; +using Microsoft.UI.Xaml.Shapes; +using System; +using System.Numerics; +using Windows.UI; + +namespace AIDevGallery.Controls; + +/// +/// A generic shimmer control that can be used to construct a beautiful loading effect. +/// +[TemplatePart(Name = PART_Shape, Type = typeof(Rectangle))] +internal partial class Shimmer : Control +{ + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DurationProperty = DependencyProperty.Register( + nameof(Duration), + typeof(object), + typeof(Shimmer), + new PropertyMetadata(defaultValue: TimeSpan.FromMilliseconds(1600), PropertyChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register( + nameof(IsActive), + typeof(bool), + typeof(Shimmer), + new PropertyMetadata(defaultValue: true, PropertyChanged)); + + /// + /// Gets or sets the animation duration + /// + public TimeSpan Duration + { + get => (TimeSpan)GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + public bool IsActive + { + get => (bool)GetValue(IsActiveProperty); + set => SetValue(IsActiveProperty, value); + } + + private const float InitialStartPointX = -7.92f; + private const string PART_Shape = "Shape"; + + private Vector2Node? _sizeAnimation; + private Vector2KeyFrameAnimation? _gradientStartPointAnimation; + private Vector2KeyFrameAnimation? _gradientEndPointAnimation; + private CompositionColorGradientStop? _gradientStop1; + private CompositionColorGradientStop? _gradientStop2; + private CompositionColorGradientStop? _gradientStop3; + private CompositionColorGradientStop? _gradientStop4; + private CompositionRoundedRectangleGeometry? _rectangleGeometry; + private ShapeVisual? _shapeVisual; + private CompositionLinearGradientBrush? _shimmerMaskGradient; + private Border? _shape; + + private bool _initialized; + private bool _animationStarted; + + public Shimmer() + { + DefaultStyleKey = typeof(Shimmer); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _shape = GetTemplateChild(PART_Shape) as Border; + if (_initialized is false && TryInitializationResource() && IsActive) + { + TryStartAnimation(); + } + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (_initialized is false && TryInitializationResource() && IsActive) + { + TryStartAnimation(); + } + + ActualThemeChanged += OnActualThemeChanged; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + ActualThemeChanged -= OnActualThemeChanged; + StopAnimation(); + + if (_initialized && _shape != null) + { + ElementCompositionPreview.SetElementChildVisual(_shape, null); + + _rectangleGeometry!.Dispose(); + _shapeVisual!.Dispose(); + _shimmerMaskGradient!.Dispose(); + _gradientStop1!.Dispose(); + _gradientStop2!.Dispose(); + _gradientStop3!.Dispose(); + _gradientStop4!.Dispose(); + + _initialized = false; + } + } + + private void OnActualThemeChanged(FrameworkElement sender, object args) + { + if (_initialized is false) + { + return; + } + + SetGradientStopColorsByTheme(); + } + + private bool TryInitializationResource() + { + if (_initialized) + { + return true; + } + + if (_shape is null || IsLoaded is false) + { + return false; + } + + var compositor = _shape.GetVisual().Compositor; + + _rectangleGeometry = compositor.CreateRoundedRectangleGeometry(); + _shapeVisual = compositor.CreateShapeVisual(); + _shimmerMaskGradient = compositor.CreateLinearGradientBrush(); + _gradientStop1 = compositor.CreateColorGradientStop(); + _gradientStop2 = compositor.CreateColorGradientStop(); + _gradientStop3 = compositor.CreateColorGradientStop(); + _gradientStop4 = compositor.CreateColorGradientStop(); + SetGradientAndStops(); + SetGradientStopColorsByTheme(); + _rectangleGeometry.CornerRadius = new Vector2((float)CornerRadius.TopLeft); + var spriteShape = compositor.CreateSpriteShape(_rectangleGeometry); + spriteShape.FillBrush = _shimmerMaskGradient; + _shapeVisual.Shapes.Add(spriteShape); + ElementCompositionPreview.SetElementChildVisual(_shape, _shapeVisual); + + _initialized = true; + return true; + } + + private void SetGradientAndStops() + { + _shimmerMaskGradient!.StartPoint = new Vector2(InitialStartPointX, 0.0f); + _shimmerMaskGradient.EndPoint = new Vector2(0.0f, 1.0f); + + _gradientStop1!.Offset = 0.273f; + _gradientStop2!.Offset = 0.436f; + _gradientStop3!.Offset = 0.482f; + _gradientStop4!.Offset = 0.643f; + + _shimmerMaskGradient.ColorStops.Add(_gradientStop1); + _shimmerMaskGradient.ColorStops.Add(_gradientStop2); + _shimmerMaskGradient.ColorStops.Add(_gradientStop3); + _shimmerMaskGradient.ColorStops.Add(_gradientStop4); + } + + private void SetGradientStopColorsByTheme() + { + switch (ActualTheme) + { + case ElementTheme.Default: + case ElementTheme.Dark: + _gradientStop1!.Color = Color.FromArgb((byte)(255 * 6.05 / 100), 255, 255, 255); + _gradientStop2!.Color = Color.FromArgb((byte)(255 * 3.26 / 100), 255, 255, 255); + _gradientStop3!.Color = Color.FromArgb((byte)(255 * 3.26 / 100), 255, 255, 255); + _gradientStop4!.Color = Color.FromArgb((byte)(255 * 6.05 / 100), 255, 255, 255); + break; + case ElementTheme.Light: + _gradientStop1!.Color = Color.FromArgb((byte)(255 * 5.37 / 100), 0, 0, 0); + _gradientStop2!.Color = Color.FromArgb((byte)(255 * 2.89 / 100), 0, 0, 0); + _gradientStop3!.Color = Color.FromArgb((byte)(255 * 2.89 / 100), 0, 0, 0); + _gradientStop4!.Color = Color.FromArgb((byte)(255 * 5.37 / 100), 0, 0, 0); + break; + } + } + + private void TryStartAnimation() + { + if (_animationStarted || _initialized is false || _shape is null || _shapeVisual is null || _rectangleGeometry is null) + { + return; + } + + var rootVisual = _shape.GetVisual(); + _sizeAnimation = rootVisual.GetReference().Size; + _shapeVisual.StartAnimation(nameof(ShapeVisual.Size), _sizeAnimation); + _rectangleGeometry.StartAnimation(nameof(CompositionRoundedRectangleGeometry.Size), _sizeAnimation); + + _gradientStartPointAnimation = rootVisual.Compositor.CreateVector2KeyFrameAnimation(); + _gradientStartPointAnimation.Duration = Duration; + _gradientStartPointAnimation.IterationBehavior = AnimationIterationBehavior.Forever; + _gradientStartPointAnimation.InsertKeyFrame(0.0f, new Vector2(InitialStartPointX, 0.0f)); + _gradientStartPointAnimation.InsertKeyFrame(1.0f, Vector2.Zero); + _shimmerMaskGradient!.StartAnimation(nameof(CompositionLinearGradientBrush.StartPoint), _gradientStartPointAnimation); + + _gradientEndPointAnimation = rootVisual.Compositor.CreateVector2KeyFrameAnimation(); + _gradientEndPointAnimation.Duration = Duration; + _gradientEndPointAnimation.IterationBehavior = AnimationIterationBehavior.Forever; + _gradientEndPointAnimation.InsertKeyFrame(0.0f, new Vector2(1.0f, 0.0f)); + _gradientEndPointAnimation.InsertKeyFrame(1.0f, new Vector2(-InitialStartPointX, 1.0f)); + _shimmerMaskGradient.StartAnimation(nameof(CompositionLinearGradientBrush.EndPoint), _gradientEndPointAnimation); + + _animationStarted = true; + } + + private void StopAnimation() + { + if (_animationStarted is false) + { + return; + } + + _shapeVisual!.StopAnimation(nameof(ShapeVisual.Size)); + _rectangleGeometry!.StopAnimation(nameof(CompositionRoundedRectangleGeometry.Size)); + _shimmerMaskGradient!.StopAnimation(nameof(CompositionLinearGradientBrush.StartPoint)); + _shimmerMaskGradient.StopAnimation(nameof(CompositionLinearGradientBrush.EndPoint)); + + _sizeAnimation!.Dispose(); + _gradientStartPointAnimation!.Dispose(); + _gradientEndPointAnimation!.Dispose(); + _animationStarted = false; + } + + private static void PropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) + { + var self = (Shimmer)s; + if (self.IsActive) + { + self.StopAnimation(); + self.TryStartAnimation(); + } + else + { + self.StopAnimation(); + } + } +} \ No newline at end of file diff --git a/AIDevGallery/Controls/Token.xaml b/AIDevGallery/Controls/Token.xaml index 267377f1..cba82eb3 100644 --- a/AIDevGallery/Controls/Token.xaml +++ b/AIDevGallery/Controls/Token.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Helpers/MarkdownHelper.cs b/AIDevGallery/Helpers/MarkdownHelper.cs new file mode 100644 index 00000000..95dbe0b1 --- /dev/null +++ b/AIDevGallery/Helpers/MarkdownHelper.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.RegularExpressions; + +namespace AIDevGallery.Helpers; + +internal static class MarkdownHelper +{ + public static string PreprocessMarkdown(string markdown) + { + markdown = Regex.Replace(markdown, @"\A---\n[\s\S]*?---\n", string.Empty, RegexOptions.Multiline); + markdown = Regex.Replace(markdown, @"^>\s*\[!IMPORTANT\]", "> **ℹ️ Important:**", RegexOptions.Multiline); + markdown = Regex.Replace(markdown, @"^>\s*\[!NOTE\]", "> **❗ Note:**", RegexOptions.Multiline); + markdown = Regex.Replace(markdown, @"^>\s*\[!TIP\]", "> **💡 Tip:**", RegexOptions.Multiline); + + return markdown; + } +} \ No newline at end of file diff --git a/AIDevGallery/Helpers/ModelDetailsHelper.cs b/AIDevGallery/Helpers/ModelDetailsHelper.cs index 1a8afeec..99b60f62 100644 --- a/AIDevGallery/Helpers/ModelDetailsHelper.cs +++ b/AIDevGallery/Helpers/ModelDetailsHelper.cs @@ -10,21 +10,23 @@ namespace AIDevGallery.Helpers; internal static class ModelDetailsHelper { - public static ModelFamily? GetFamily(this ModelDetails modelDetails) + public static bool EqualOrParent(ModelType modelType, ModelType searchModelType) { - if (ModelTypeHelpers.ModelDetails.Any(md => md.Value.Url == modelDetails.Url)) + if (modelType == searchModelType) { - var myKey = ModelTypeHelpers.ModelDetails.FirstOrDefault(md => md.Value.Url == modelDetails.Url).Key; + return true; + } - if (ModelTypeHelpers.ParentMapping.Values.Any(parent => parent.Contains(myKey))) + while (ModelTypeHelpers.ParentMapping.Values.Any(parent => parent.Contains(modelType))) + { + modelType = ModelTypeHelpers.ParentMapping.FirstOrDefault(parent => parent.Value.Contains(modelType)).Key; + if (modelType == searchModelType) { - var parentKey = ModelTypeHelpers.ParentMapping.FirstOrDefault(parent => parent.Value.Contains(myKey)).Key; - var parent = ModelTypeHelpers.ModelFamilyDetails[parentKey]; - return parent; + return true; } } - return null; + return false; } public static ModelDetails GetModelDetailsFromApiDefinition(ModelType modelType, ApiDefinition apiDefinition) @@ -34,7 +36,7 @@ public static ModelDetails GetModelDetailsFromApiDefinition(ModelType modelType, Id = apiDefinition.Id, Icon = apiDefinition.Icon, Name = apiDefinition.Name, - HardwareAccelerators = [HardwareAccelerator.DML], + HardwareAccelerators = [HardwareAccelerator.WCRAPI], IsUserAdded = false, SupportedOnQualcomm = true, ReadmeUrl = apiDefinition.ReadmeUrl, diff --git a/AIDevGallery/Helpers/URLHelper.cs b/AIDevGallery/Helpers/URLHelper.cs new file mode 100644 index 00000000..4a2741ff --- /dev/null +++ b/AIDevGallery/Helpers/URLHelper.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; + +namespace AIDevGallery.Helpers; + +internal static class URLHelper +{ + private const string DocsBaseUrl = "https://learn.microsoft.com/"; + private const string WcrDocsRelativePath = "/windows/ai/apis/"; + + public static bool IsValidUrl(string url) + { + Uri uri; + return Uri.TryCreate(url, UriKind.Absolute, out uri!) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); + } + + public static string FixWcrReadmeLink(string link) + { + if (link.StartsWith('/')) + { + return Path.Join(DocsBaseUrl, link); + } + else + { + return Path.Join(DocsBaseUrl, WcrDocsRelativePath, link.Replace(".md", string.Empty)); + } + } +} \ No newline at end of file diff --git a/AIDevGallery/MainWindow.xaml b/AIDevGallery/MainWindow.xaml index c6144c76..b253f273 100644 --- a/AIDevGallery/MainWindow.xaml +++ b/AIDevGallery/MainWindow.xaml @@ -99,6 +99,10 @@ Content="Models" Icon="{ui:FontIcon Glyph=}" Tag="Models" /> + { - if (page == typeof(ScenarioSelectionPage) && NavFrame.Content is ScenarioSelectionPage scenarioPage && 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); + } + else if (page == typeof(ScenarioSelectionPage) && NavFrame.Content is ScenarioSelectionPage scenarioPage && param != null) { // No need to navigate to the ScenarioSelectionPage again, we just want to navigate to the right subpage scenarioPage.HandleNavigation(param); @@ -100,13 +108,17 @@ private void Navigate(Type page, object? param = null) { if (param == null && NavFrame.Content != null && NavFrame.Content.GetType() == page) { - if (NavFrame.Content is ScenarioSelectionPage page) + if (NavFrame.Content is ScenarioSelectionPage scenario) { - page.ShowHideNavPane(); + scenario.ShowHideNavPane(); } - else if (NavFrame.Content is ModelSelectionPage modelPage) + else if (NavFrame.Content is ModelSelectionPage model) { - modelPage.ShowHideNavPane(); + model.ShowHideNavPane(); + } + else if (NavFrame.Content is APISelectionPage api) + { + api.ShowHideNavPane(); } return; @@ -208,6 +220,10 @@ private void NavFrame_Navigating(object sender, Microsoft.UI.Xaml.Navigation.Nav { NavView.SelectedItem = NavView.MenuItems[2]; } + else if (e.SourcePageType == typeof(APISelectionPage)) + { + NavView.SelectedItem = NavView.MenuItems[3]; + } else if (e.SourcePageType == typeof(SettingsPage)) { NavView.SelectedItem = NavView.FooterMenuItems[1]; diff --git a/AIDevGallery/Models/BaseSampleNavigationParameters.cs b/AIDevGallery/Models/BaseSampleNavigationParameters.cs index 18f05b37..5a4d8a38 100644 --- a/AIDevGallery/Models/BaseSampleNavigationParameters.cs +++ b/AIDevGallery/Models/BaseSampleNavigationParameters.cs @@ -22,6 +22,11 @@ public void NotifyCompletion() public async Task GetIChatClientAsync() { + if (ChatClientModelPath == $"file://{ModelType.PhiSilica}") + { + return await PhiSilicaClient.CreateAsync(CancellationToken).ConfigureAwait(false); + } + return await GenAIModel.CreateAsync(ChatClientModelPath, ChatClientPromptTemplate, CancellationToken).ConfigureAwait(false); } diff --git a/AIDevGallery/Models/ModelCompatibility.cs b/AIDevGallery/Models/ModelCompatibility.cs index 3705740d..ca2937d4 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 (AppUtils.HasNpu() && 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; @@ -43,7 +59,7 @@ public static ModelCompatibility GetModelCompatibility(ModelDetails modelDetails else if (modelDetails.Size + BytesInGB < vram) { compatibility = ModelCompatibilityState.NotRecomended; - description = $"This model is not recomended for your device. We recommend minimum {minimumSizeNeeded + 2}GB of dedicated GPU memory. Your GPU has {vramInGb}GB"; + description = $"This model is not recommended for your device. We recommend minimum {minimumSizeNeeded + 2}GB of dedicated GPU memory. Your GPU has {vramInGb}GB"; } else { diff --git a/AIDevGallery/Models/SampleNavigationParameters.cs b/AIDevGallery/Models/SampleNavigationParameters.cs index 7089f151..cdd4a799 100644 --- a/AIDevGallery/Models/SampleNavigationParameters.cs +++ b/AIDevGallery/Models/SampleNavigationParameters.cs @@ -20,6 +20,7 @@ internal class SampleNavigationParameters( { public string ModelPath { get; } = modelPath; public HardwareAccelerator HardwareAccelerator { get; } = hardwareAccelerator; + public string SampleId => sampleId; protected override string ChatClientModelPath => ModelPath; protected override LlmPromptTemplate? ChatClientPromptTemplate => promptTemplate; diff --git a/AIDevGallery/Models/Samples.cs b/AIDevGallery/Models/Samples.cs index b5290eba..a1a9dc0b 100644 --- a/AIDevGallery/Models/Samples.cs +++ b/AIDevGallery/Models/Samples.cs @@ -49,8 +49,11 @@ internal class ApiDefinition public string Id { get; init; } = null!; public string Name { get; init; } = null!; public string Icon { get; init; } = null!; + public string IconGlyph { get; init; } = null!; + public string Description { get; init; } = null!; public string ReadmeUrl { get; init; } = null!; public string License { get; init; } = null!; + public string SampleIdToShowInDocs { get; set; } = null!; } internal class ModelDetails @@ -155,7 +158,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/APIs/APIOverview.xaml b/AIDevGallery/Pages/APIs/APIOverview.xaml new file mode 100644 index 00000000..d52ca58f --- /dev/null +++ b/AIDevGallery/Pages/APIs/APIOverview.xaml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Pages/APIs/APIOverview.xaml.cs b/AIDevGallery/Pages/APIs/APIOverview.xaml.cs new file mode 100644 index 00000000..65afdb7f --- /dev/null +++ b/AIDevGallery/Pages/APIs/APIOverview.xaml.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using AIDevGallery.Samples; +using Microsoft.UI.Xaml.Controls; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace AIDevGallery.Pages; +internal sealed partial class APIOverview : Page +{ + private ObservableCollection wcrAPIs = new(); + + public APIOverview() + { + this.InitializeComponent(); + SetupAPIs(); + } + + private void SetupAPIs() + { + wcrAPIs.Clear(); + if (ModelTypeHelpers.ParentMapping.TryGetValue(ModelType.WCRAPIs, out List? innerItems)) + { + foreach (var item in innerItems) + { + if (ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(item, out var apiDefinition)) + { + wcrAPIs.Add(apiDefinition); + } + } + } + } + + private void APIViewer_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args) + { + // TO DO: there must be a better way to do this? + if (args.InvokedItem is ApiDefinition api && ModelTypeHelpers.ParentMapping.TryGetValue(ModelType.WCRAPIs, out List? innerItems)) + { + foreach (var item in innerItems) + { + if (ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(item, out var apiDefinition)) + { + if (apiDefinition == api) + { + App.MainWindow.Navigate("apis", item); + } + } + } + } + } +} \ No newline at end of file diff --git a/AIDevGallery/Pages/APIs/APIPage.xaml b/AIDevGallery/Pages/APIs/APIPage.xaml new file mode 100644 index 00000000..6b632a46 --- /dev/null +++ b/AIDevGallery/Pages/APIs/APIPage.xaml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AIDevGallery/Pages/APIs/APIPage.xaml.cs b/AIDevGallery/Pages/APIs/APIPage.xaml.cs new file mode 100644 index 00000000..370ef7a7 --- /dev/null +++ b/AIDevGallery/Pages/APIs/APIPage.xaml.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Helpers; +using AIDevGallery.Models; +using AIDevGallery.Samples; +using AIDevGallery.Telemetry.Events; +using AIDevGallery.Utils; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; + +namespace AIDevGallery.Pages; + +internal sealed partial class APIPage : Page +{ + public ModelFamily? ModelFamily { get; set; } + private ModelType? modelFamilyType; + + public APIPage() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + if (e.Parameter is MostRecentlyUsedItem mru) + { + var modelFamilyId = mru.ItemId; + } + else if (e.Parameter is ModelType modelType && ModelTypeHelpers.ModelFamilyDetails.TryGetValue(modelType, out var modelFamilyDetails)) + { + modelFamilyType = modelType; + ModelFamily = modelFamilyDetails; + } + else if (e.Parameter is ModelDetails details) + { + ModelFamily = new ModelFamily + { + Id = details.Id, + DocsUrl = details.ReadmeUrl ?? string.Empty, + ReadmeUrl = details.ReadmeUrl ?? string.Empty, + Name = details.Name + }; + } + else if (e.Parameter is ModelType apiType && ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(apiType, out var apiDefinition)) + { + // API + modelFamilyType = apiType; + + ModelFamily = new ModelFamily + { + Id = apiDefinition.Id, + ReadmeUrl = apiDefinition.ReadmeUrl, + DocsUrl = apiDefinition.ReadmeUrl, + Name = apiDefinition.Name, + }; + + if (!string.IsNullOrWhiteSpace(apiDefinition.SampleIdToShowInDocs)) + { + var sample = SampleDetails.Samples.FirstOrDefault(s => s.Id == apiDefinition.SampleIdToShowInDocs); + if (sample != null) + { + _ = sampleContainer.LoadSampleAsync(sample, [ModelDetailsHelper.GetModelDetailsFromApiDefinition(apiType, apiDefinition)]); + } + } + else + { + sampleContainerRoot.Visibility = Visibility.Collapsed; + } + + WcrApiCodeSnippet.Snippets.TryGetValue(apiType, out var snippet); + if (snippet != null) + { + CodeSampleTextBlock.Text = $"```csharp\r\n{snippet}\r\n```"; + } + else + { + CodeCard.Visibility = Visibility.Collapsed; + } + } + else + { + throw new InvalidOperationException("Invalid navigation parameter"); + } + + if (ModelFamily != null && !string.IsNullOrWhiteSpace(ModelFamily.ReadmeUrl)) + { + var loadReadme = LoadReadme(ModelFamily.ReadmeUrl); + } + else + { + DocumentationCard.Visibility = Visibility.Collapsed; + } + + GetSamples(); + } + + private void GetSamples() + { + // if we don't have a modelType, we are in a user added language model, use same samples as Phi + var modelType = modelFamilyType ?? ModelType.Phi3Mini; + + var samples = SampleDetails.Samples.Where(s => s.Model1Types.Contains(modelType) || s.Model2Types?.Contains(modelType) == true).ToList(); + if (ModelTypeHelpers.ParentMapping.Values.Any(parent => parent.Contains(modelType))) + { + var parent = ModelTypeHelpers.ParentMapping.FirstOrDefault(parent => parent.Value.Contains(modelType)).Key; + samples.AddRange(SampleDetails.Samples.Where(s => s.Model1Types.Contains(parent) || s.Model2Types?.Contains(parent) == true)); + } + + SampleList.ItemsSource = samples; + } + + private async Task LoadReadme(string url) + { + string readmeContents = await GithubApi.GetContentsOfTextFile(url); + if (!string.IsNullOrWhiteSpace(readmeContents)) + { + readmeContents = MarkdownHelper.PreprocessMarkdown(readmeContents); + + markdownTextBlock.Text = readmeContents; + } + + readmeProgressRing.IsActive = false; + } + + private void CopyButton_Click(object sender, RoutedEventArgs e) + { + if (ModelFamily == null || ModelFamily.Id == null) + { + return; + } + + var dataPackage = new DataPackage(); + dataPackage.SetText($"aidevgallery://apis/{ModelFamily.Id}"); + Clipboard.SetContentWithOptions(dataPackage, null); + } + + private void MarkdownTextBlock_LinkClicked(object sender, CommunityToolkit.WinUI.UI.Controls.LinkClickedEventArgs e) + { + string link = e.Link; + + if (!URLHelper.IsValidUrl(link)) + { + link = URLHelper.FixWcrReadmeLink(link); + } + + ModelDetailsLinkClickedEvent.Log(link); + Process.Start(new ProcessStartInfo() + { + FileName = link, + UseShellExecute = true + }); + } + + private void SampleList_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args) + { + if (args.InvokedItem is Sample sample) + { + App.MainWindow.Navigate("Samples", new SampleNavigationArgs(sample)); + } + } + + private void Page_Loaded(object sender, RoutedEventArgs e) + { + BackgroundShadow.Receivers.Add(ShadowCastGrid); + } +} \ No newline at end of file diff --git a/AIDevGallery/Pages/APIs/APISelectionPage.xaml b/AIDevGallery/Pages/APIs/APISelectionPage.xaml new file mode 100644 index 00000000..40fa48ea --- /dev/null +++ b/AIDevGallery/Pages/APIs/APISelectionPage.xaml @@ -0,0 +1,47 @@ + + + + + + 1,0,0,0 + 0,0,0,0 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AIDevGallery/Pages/APIs/APISelectionPage.xaml.cs b/AIDevGallery/Pages/APIs/APISelectionPage.xaml.cs new file mode 100644 index 00000000..e7555b00 --- /dev/null +++ b/AIDevGallery/Pages/APIs/APISelectionPage.xaml.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using AIDevGallery.Samples; +using AIDevGallery.Telemetry.Events; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using System.Collections.Generic; +using System.Linq; + +namespace AIDevGallery.Pages; + +internal sealed partial class APISelectionPage : Page +{ + public APISelectionPage() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + NavigatedToPageEvent.Log(nameof(APISelectionPage)); + SetupAPIs(); + NavView.Loaded += (sender, args) => + { + if (e.Parameter is ModelType 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 + { + NavView.SelectedItem = NavView.MenuItems[0]; + } + }; + } + + private void SetupAPIs() + { + if (ModelTypeHelpers.ParentMapping.TryGetValue(ModelType.WCRAPIs, out List? innerItems)) + { + foreach (var item in innerItems) + { + if (ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(item, out var apiDefinition)) + { + NavView.MenuItems.Add(new NavigationViewItem() { Content = apiDefinition.Name, Icon = new FontIcon() { Glyph = apiDefinition.IconGlyph }, Tag = item }); + } + } + } + } + + private void NavView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is NavigationViewItem item) + { + if (item.Tag is ModelType type) + { + NavFrame.Navigate(typeof(APIPage), type); + } + else + { + NavFrame.Navigate(typeof(APIOverview)); + } + } + } + + public void SetSelectedApiInMenu(ModelType selectedType) + { + foreach (var item in NavView.MenuItems) + { + if (item is NavigationViewItem navItem && navItem.Tag is ModelType mt && mt == selectedType) + { + NavView.SelectedItem = navItem; + return; + } + } + } + + public void ShowHideNavPane() + { + NavView.OpenPaneLength = NavView.OpenPaneLength == 0 ? 224 : 0; + } +} \ No newline at end of file diff --git a/AIDevGallery/Pages/HomePage.xaml b/AIDevGallery/Pages/HomePage.xaml index 34e1286b..9747aef2 100644 --- a/AIDevGallery/Pages/HomePage.xaml +++ b/AIDevGallery/Pages/HomePage.xaml @@ -2,21 +2,9 @@ x:Class="AIDevGallery.Pages.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:animations="using:CommunityToolkit.WinUI.Animations" - xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors" xmlns:controls="using:AIDevGallery.Controls" - xmlns:converters="using:AIDevGallery.Converters" - xmlns:core="using:Microsoft.Xaml.Interactions.Core" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" - xmlns:interactivity="using:Microsoft.Xaml.Interactivity" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:models="using:AIDevGallery.Models" - xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" - xmlns:ui="using:CommunityToolkit.WinUI" - xmlns:utils="using:AIDevGallery.Utils" - xmlns:wuc="using:WinUICommunity" Loaded="Page_Loaded" mc:Ignorable="d"> diff --git a/AIDevGallery/Pages/Models/ModelPage.xaml b/AIDevGallery/Pages/Models/ModelPage.xaml index b8a86037..a9a70b75 100644 --- a/AIDevGallery/Pages/Models/ModelPage.xaml +++ b/AIDevGallery/Pages/Models/ModelPage.xaml @@ -3,14 +3,10 @@ x:Class="AIDevGallery.Pages.ModelPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:Chat="using:AIDevGallery.Samples.OpenSourceModels.LanguageModels" - xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters" - xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:toolkit2="using:CommunityToolkit.WinUI.UI.Controls" xmlns:types="using:AIDevGallery.Models" xmlns:ui="using:CommunityToolkit.WinUI" @@ -20,148 +16,128 @@ - + - + + + + - - - - - - - - - - + - + - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - + Icon="{ui:FontIcon Glyph=, + FontSize=16}"> - - - + - - - - - - + VerticalContentAlignment="Top" + Icon="{ui:FontIcon Glyph=, + FontSize=16}"> - - - @@ -204,10 +179,9 @@ - + - @@ -220,17 +194,14 @@ - - - - - - + + + + - - + diff --git a/AIDevGallery/Pages/Models/ModelPage.xaml.cs b/AIDevGallery/Pages/Models/ModelPage.xaml.cs index 21a731cb..d496a9ef 100644 --- a/AIDevGallery/Pages/Models/ModelPage.xaml.cs +++ b/AIDevGallery/Pages/Models/ModelPage.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using AIDevGallery.Helpers; @@ -13,7 +13,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Windows.ApplicationModel.DataTransfer; @@ -23,7 +22,6 @@ internal sealed partial class ModelPage : Page { public ModelFamily? ModelFamily { get; set; } private ModelType? modelFamilyType; - public bool IsNotApi => !modelFamilyType.HasValue || !ModelTypeHelpers.ApiDefinitionDetails.ContainsKey(modelFamilyType.Value); public ModelPage() { @@ -58,21 +56,6 @@ protected override void OnNavigatedTo(NavigationEventArgs e) Name = details.Name }; } - else if (e.Parameter is ModelType apiType && ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(apiType, out var apiDefinition)) - { - // API - modelFamilyType = apiType; - - ModelFamily = new ModelFamily - { - Id = apiDefinition.Id, - ReadmeUrl = apiDefinition.ReadmeUrl, - DocsUrl = apiDefinition.ReadmeUrl, - Name = apiDefinition.Name, - }; - - modelSelectionControl.SetModels(GetAllSampleDetails().ToList()); - } else { throw new InvalidOperationException("Invalid navigation parameter"); @@ -84,7 +67,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) } else { - summaryGrid.Visibility = Visibility.Collapsed; + DocumentationCard.Visibility = Visibility.Collapsed; } EnableSampleListIfModelIsDownloaded(); @@ -130,7 +113,8 @@ private async Task LoadReadme(string url) if (!string.IsNullOrWhiteSpace(readmeContents)) { - readmeContents = Regex.Replace(readmeContents, @"\A---\n[\s\S]*?---\n", string.Empty, RegexOptions.Multiline); + readmeContents = MarkdownHelper.PreprocessMarkdown(readmeContents); + markdownTextBlock.Text = readmeContents; } @@ -144,22 +128,12 @@ private IEnumerable GetAllSampleDetails() yield break; } - if (modelTypes.Count == 0) - { - // Its an API - modelTypes = [modelFamilyType.Value]; - } - foreach (var modelType in modelTypes) { if (ModelTypeHelpers.ModelDetails.TryGetValue(modelType, out var modelDetails)) { yield return modelDetails; } - else if (ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(modelType, out var apiDefinition)) - { - yield return ModelDetailsHelper.GetModelDetailsFromApiDefinition(modelType, apiDefinition); - } } } @@ -192,10 +166,12 @@ private void CopyButton_Click(object sender, RoutedEventArgs e) private void MarkdownTextBlock_LinkClicked(object sender, CommunityToolkit.WinUI.UI.Controls.LinkClickedEventArgs e) { - ModelDetailsLinkClickedEvent.Log(e.Link); + string link = e.Link; + + ModelDetailsLinkClickedEvent.Log(link); Process.Start(new ProcessStartInfo() { - FileName = e.Link, + FileName = link, UseShellExecute = true }); } diff --git a/AIDevGallery/Pages/Models/ModelSelectionPage.xaml b/AIDevGallery/Pages/Models/ModelSelectionPage.xaml index 5635b40f..1aa3fd52 100644 --- a/AIDevGallery/Pages/Models/ModelSelectionPage.xaml +++ b/AIDevGallery/Pages/Models/ModelSelectionPage.xaml @@ -4,9 +4,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="using:CommunityToolkit.WinUI" mc:Ignorable="d"> 1,0,0,0 0,0,0,0 - diff --git a/AIDevGallery/Pages/Models/ModelSelectionPage.xaml.cs b/AIDevGallery/Pages/Models/ModelSelectionPage.xaml.cs index 3f825fd6..766d49e0 100644 --- a/AIDevGallery/Pages/Models/ModelSelectionPage.xaml.cs +++ b/AIDevGallery/Pages/Models/ModelSelectionPage.xaml.cs @@ -106,14 +106,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) public void ShowHideNavPane() { - if (NavView.OpenPaneLength == 0) - { - NavView.OpenPaneLength = 248; - } - else - { - NavView.OpenPaneLength = 0; - } + NavView.OpenPaneLength = NavView.OpenPaneLength == 0 ? 224 : 0; } private void SetUpModels() @@ -138,12 +131,15 @@ private void SetUpModels() foreach (var key in rootModels.OrderBy(ModelTypeHelpers.GetModelOrder)) { - var navItem = CreateFromItem(key, ModelTypeHelpers.ModelGroupDetails.ContainsKey(key)); - NavView.MenuItems.Add(navItem); - - if (key == ModelType.LanguageModels) + if (key != ModelType.WCRAPIs) { - languageModelsNavItem = navItem; + var navItem = CreateFromItem(key, ModelTypeHelpers.ModelGroupDetails.ContainsKey(key)); + NavView.MenuItems.Add(navItem); + + if (key == ModelType.LanguageModels) + { + languageModelsNavItem = navItem; + } } } diff --git a/AIDevGallery/Pages/Scenarios/ScenarioOverviewPage.xaml b/AIDevGallery/Pages/Scenarios/ScenarioOverviewPage.xaml index 8661ef9b..fe358928 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioOverviewPage.xaml +++ b/AIDevGallery/Pages/Scenarios/ScenarioOverviewPage.xaml @@ -9,7 +9,6 @@ xmlns:media="using:CommunityToolkit.WinUI.Media" xmlns:models="using:AIDevGallery.Models" xmlns:ui="using:CommunityToolkit.WinUI" - xmlns:util="using:AIDevGallery.Utils" mc:Ignorable="d"> @@ -19,14 +18,10 @@ - - - - - - - - + - + - + - @@ -103,7 +91,7 @@ Grid.Row="2" IsItemInvokedEnabled="True" ItemInvoked="ScenarioItemsView_ItemInvoked" - ItemsSource="{x:Bind Scenarios, Mode=OneWay}" + ItemsSource="{x:Bind Scenarios}" SelectionMode="None"> diff --git a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml index 8ba2932b..b6d7e4e1 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml +++ b/AIDevGallery/Pages/Scenarios/ScenarioPage.xaml @@ -5,9 +5,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:AIDevGallery.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI" mc:Ignorable="d"> @@ -125,7 +123,12 @@ x:Name="contentHost" Grid.Row="1" HorizontalAlignment="Stretch"> - + sample.Scenario == scenario.ScenarioType).ToList(); - List>>? modelsDetailsDict = null; if (samples.Count == 0) { return; } - else if (samples.Count == 1) - { - modelsDetailsDict = ModelDetailsHelper.GetModelDetails(samples.First()); - } - else - { - var sample = samples.First(); - var modelKey = sample.Model1Types.First(); // First only? - - if (ModelTypeHelpers.ParentMapping.Values.Any(parent => parent.Contains(modelKey))) - { - var parentKey = ModelTypeHelpers.ParentMapping.FirstOrDefault(parent => parent.Value.Contains(modelKey)).Key; - var listModelDetails = new List(); + List modelDetailsList = new(); + List modelDetailsList2 = new(); - foreach (var s in samples) - { - listModelDetails.AddRange(ModelDetailsHelper.GetModelDetails(s).First().First().Value); - } - - modelsDetailsDict = - [ - new Dictionary> - { - [parentKey] = listModelDetails - } - ]; - } + foreach (var s in samples) + { + var models = ModelDetailsHelper.GetModelDetails(s); - if (sample.Model2Types != null) + // Model1Types + if (models.Count > 0) { - var modelKey2 = sample.Model2Types.First(); // First only? + modelDetailsList.AddRange(models.First().Values.SelectMany(list => list).ToList()); - if (ModelTypeHelpers.ParentMapping.Values.Any(parent => parent.Contains(modelKey2))) + // Model2Types + if (models.Count > 1) { - var parentKey2 = ModelTypeHelpers.ParentMapping.FirstOrDefault(parent => parent.Value.Contains(modelKey2)).Key; - - var listModelDetails2 = new List(); - - foreach (var s in samples) - { - listModelDetails2.AddRange(ModelDetailsHelper.GetModelDetails(s).ElementAt(1).First().Value); - } - - modelsDetailsDict ??= []; - - modelsDetailsDict.Add( - new Dictionary> - { - [parentKey2] = listModelDetails2 - }); + modelDetailsList2.AddRange(models[1].Values.SelectMany(list => list).ToList()); } } } - if (modelsDetailsDict == null) + if (modelDetailsList.Count == 0) { return; } - var models = modelsDetailsDict.First().SelectMany(g => g.Value).ToList(); - - selectedModelDetails = SelectLatestOrDefault(models); - - if (modelsDetailsDict.Count > 1) + if (modelDetailsList2.Count > 1) { - var models2 = modelsDetailsDict.ElementAt(1).SelectMany(g => g.Value).ToList(); - selectedModelDetails2 = SelectLatestOrDefault(models2); - modelSelectionControl2.SetModels(models2, initialModelToLoad); + selectedModelDetails2 = SelectLatestOrDefault(modelDetailsList2); + modelSelectionControl2.SetModels(modelDetailsList2, initialModelToLoad); } - modelSelectionControl.SetModels(models, initialModelToLoad); + selectedModelDetails = SelectLatestOrDefault(modelDetailsList); + modelSelectionControl.SetModels(modelDetailsList, initialModelToLoad); UpdateModelSelectionPlaceholderControl(); } diff --git a/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml b/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml index d07a60b2..29bf2700 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml +++ b/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml.cs b/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml.cs index caa3a41f..c57c3345 100644 --- a/AIDevGallery/Pages/Scenarios/ScenarioSelectionPage.xaml.cs +++ b/AIDevGallery/Pages/Scenarios/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 Scenario? selectedScenario; @@ -100,14 +99,7 @@ public void HandleNavigation(object? obj) public void ShowHideNavPane() { - if (NavView.OpenPaneLength == 0) - { - NavView.OpenPaneLength = 248; - } - else - { - NavView.OpenPaneLength = 0; - } + NavView.OpenPaneLength = NavView.OpenPaneLength == 0 ? 248 : 0; } private void SetUpScenarios(string? filter = null) diff --git a/AIDevGallery/Pages/SettingsPage.xaml b/AIDevGallery/Pages/SettingsPage.xaml index 9a4bd49a..ddab5f49 100644 --- a/AIDevGallery/Pages/SettingsPage.xaml +++ b/AIDevGallery/Pages/SettingsPage.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Pages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="using:AIDevGallery.Models" xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" diff --git a/AIDevGallery/ProjectGenerator/Generator.cs b/AIDevGallery/ProjectGenerator/Generator.cs index 5821cd5e..e3439653 100644 --- a/AIDevGallery/ProjectGenerator/Generator.cs +++ b/AIDevGallery/ProjectGenerator/Generator.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using AIDevGallery.Helpers; using AIDevGallery.Models; using AIDevGallery.Samples; using AIDevGallery.Telemetry.Events; @@ -119,8 +120,7 @@ private async Task GenerateAsyncInternal(Sample sample, Dictionary modelInfos = []; + Dictionary modelInfos = []; string model1Id = string.Empty; string model2Id = string.Empty; foreach (var modelType in modelTypes) @@ -133,6 +133,7 @@ private async Task GenerateAsyncInternal(Sample sample, Dictionary GenerateAsyncInternal(Sample sample, Dictionary mf.Value.Url == modelInfo.ModelUrl) is var modelDetails2 && modelDetails2.Value != null) { modelPromptTemplate = modelDetails2.Value.PromptTemplate; - if (modelPromptTemplate != null) - { - addLllmTypes = true; - } - modelId = modelDetails2.Value.Id; } else if (ModelTypeHelpers.ApiDefinitionDetails.TryGetValue(modelType, out var apiDefinitionDetails)) @@ -193,7 +189,13 @@ private async Task GenerateAsyncInternal(Sample sample, Dictionary GenerateAsyncInternal(Sample sample, Dictionary packageReferences.PackageName == genAiPackage)) + { + packageReferences.Add((genAiPackage, null)); + } + } } SampleProjectGeneratedEvent.Log(sample.Id, model1Id, model2Id, copyModelLocally); @@ -221,7 +232,7 @@ private async Task GenerateAsyncInternal(Sample sample, Dictionary styles = []; - foreach (var file in Directory.GetFiles(outputPath, "*.xaml", SearchOption.TopDirectoryOnly)) + foreach (var file in Directory.GetFiles(Path.Join(outputPath, "Utils"), "*.xaml", SearchOption.TopDirectoryOnly)) { var content = await File.ReadAllTextAsync(file, cancellationToken); if (!content.StartsWith("", - string.Join(Environment.NewLine, styles.Select(s => $" "))); + " ", + string.Join(Environment.NewLine, styles.Select(s => $" "))); await File.WriteAllTextAsync(appXamlPath, appXaml, cancellationToken); } return outputPath; } - private string GetChatClientLoaderString(Sample sample, string modelPath, string promptTemplate) + private string? GetChatClientLoaderString(List sharedCode, string modelPath, string promptTemplate, bool isPhiSilica, ModelType modelType) { - if (!sample.SharedCode.Contains(SharedCodeEnum.GenAIModel)) + bool isLanguageModel = ModelDetailsHelper.EqualOrParent(modelType, ModelType.LanguageModels); + if (!sharedCode.Contains(SharedCodeEnum.GenAIModel) && !isPhiSilica && !isLanguageModel) + { + return null; + } + + if (isPhiSilica) { - return string.Empty; + return "PhiSilicaClient.CreateAsync()"; } return $"GenAIModel.CreateAsync({modelPath}, {promptTemplate})"; @@ -510,16 +527,31 @@ private async Task AddFilesFromSampleAsync( List<(string PackageName, string? Version)> packageReferences, string baseNamespace, string outputPath, - bool addLllmTypes, - Dictionary modelInfos, + Dictionary modelInfos, CancellationToken cancellationToken) { - var sharedCode = sample.SharedCode.ToList(); - if (!sharedCode.Contains(SharedCodeEnum.LlmPromptTemplate) && - (addLllmTypes || sample.SharedCode.Contains(SharedCodeEnum.GenAIModel))) + List sharedCode = sample.SharedCode.ToList(); + bool isLanguageModel = ModelDetailsHelper.EqualOrParent(modelInfos.Keys.First(), ModelType.LanguageModels); + + if (isLanguageModel && !sharedCode.Contains(SharedCodeEnum.GenAIModel)) { - // Always used inside GenAIModel.cs - sharedCode.Add(SharedCodeEnum.LlmPromptTemplate); + sharedCode.Add(SharedCodeEnum.GenAIModel); + } + + if (sharedCode.Contains(SharedCodeEnum.GenAIModel)) + { + if (!sharedCode.Contains(SharedCodeEnum.LlmPromptTemplate)) + { + sharedCode.Add(SharedCodeEnum.LlmPromptTemplate); + } + } + + if (modelInfos.Values.Any(mi => mi.IsPhiSilica)) + { + if (!sharedCode.Contains(SharedCodeEnum.PhiSilicaClient)) + { + sharedCode.Add(SharedCodeEnum.PhiSilicaClient); + } } if (sharedCode.Contains(SharedCodeEnum.DeviceUtils) && !sharedCode.Contains(SharedCodeEnum.NativeMethods)) @@ -575,6 +607,7 @@ private async Task AddFilesFromSampleAsync( { var cleanCsSource = CleanCsSource(sample.CSCode, baseNamespace, true); cleanCsSource = cleanCsSource.Replace("sampleParams.NotifyCompletion();", "App.Window?.ModelLoaded();"); + cleanCsSource = cleanCsSource.Replace("sampleParams.ShowWcrModelLoadingMessage = true;", string.Empty); cleanCsSource = cleanCsSource.Replace($"{className} : BaseSamplePage", "Sample : Microsoft.UI.Xaml.Controls.Page"); cleanCsSource = cleanCsSource.Replace($"public {className}()", "public Sample()"); cleanCsSource = cleanCsSource.Replace( @@ -616,15 +649,16 @@ private async Task AddFilesFromSampleAsync( var subStr = cleanCsSource[(newLineIndex + Environment.NewLine.Length)..]; var subStrWithoutSpaces = subStr.TrimStart(); var spaceCount = subStr.Length - subStrWithoutSpaces.Length; - var promptTemplate = GetPromptTemplateString(modelInfos.Values.First().ModelPromptTemplate, spaceCount); - var chatClientLoader = GetChatClientLoaderString(sample, modelPath, promptTemplate); + var modelInfo = modelInfos.Values.First(); + var promptTemplate = GetPromptTemplateString(modelInfo.ModelPromptTemplate, spaceCount); + var chatClientLoader = GetChatClientLoaderString(sharedCode, modelPath, promptTemplate, modelInfo.IsPhiSilica, modelInfos.Keys.First()); if (chatClientLoader != null) { cleanCsSource = cleanCsSource.Replace(search, chatClientLoader); } } - if (sample.SharedCode.Contains(SharedCodeEnum.GenAIModel)) + if (sharedCode.Contains(SharedCodeEnum.GenAIModel)) { cleanCsSource = RegexInitializeComponent().Replace(cleanCsSource, $"$1this.InitializeComponent();$1GenAIModel.InitializeGenAI();"); } @@ -653,10 +687,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/App.xaml b/AIDevGallery/ProjectGenerator/Template/App.xaml index 56fea38a..63a4c9c3 100644 --- a/AIDevGallery/ProjectGenerator/Template/App.xaml +++ b/AIDevGallery/ProjectGenerator/Template/App.xaml @@ -1,4 +1,4 @@ - + - + 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/ModelsDefinitions/embeddings.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/embeddings.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/embeddings.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/embeddings.modelgroup.json diff --git a/AIDevGallery/Samples/ModelsDefinitions/generativemodels.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/generativemodels.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/generativemodels.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/generativemodels.modelgroup.json diff --git a/AIDevGallery/Samples/ModelsDefinitions/imagemodels.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/imagemodels.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/imagemodels.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/imagemodels.modelgroup.json diff --git a/AIDevGallery/Samples/ModelsDefinitions/languagemodels.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/languagemodels.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json diff --git a/AIDevGallery/Samples/ModelsDefinitions/multimodal.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/multimodal.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/multimodal.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/multimodal.modelgroup.json diff --git a/AIDevGallery/Samples/ModelsDefinitions/whisper.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/whisper.modelgroup.json similarity index 100% rename from AIDevGallery/Samples/ModelsDefinitions/whisper.modelgroup.json rename to AIDevGallery/Samples/Definitions/Models/whisper.modelgroup.json diff --git a/AIDevGallery/Samples/Definitions/WcrApis/WcrApiCodeSnippet.cs b/AIDevGallery/Samples/Definitions/WcrApis/WcrApiCodeSnippet.cs new file mode 100644 index 00000000..f724cf69 --- /dev/null +++ b/AIDevGallery/Samples/Definitions/WcrApis/WcrApiCodeSnippet.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using System.Collections.Generic; + +namespace AIDevGallery.Samples; + +internal static class WcrApiCodeSnippet +{ + public static readonly Dictionary Snippets = new Dictionary + { + { + ModelType.PhiSilica, """" + using Microsoft.Windows.AI.Generative; + + if (!LanguageModel.IsAvailable()) + { + var op = await LanguageModel.MakeAvailableAsync(); + } + + using LanguageModel languageModel = await LanguageModel.CreateAsync(); + + string prompt = "Provide the molecular formula for glucose."; + + var result = await languageModel.GenerateResponseAsync(prompt); + + Console.WriteLine(result.Response); + """" + }, + { + ModelType.TextRecognitionOCR, """" + using Microsoft.Windows.Vision; + using Microsoft.Graphics.Imaging; + + if (!TextRecognizer.IsAvailable()) + { + var op = await TextRecognizer.MakeAvailableAsync(); + } + + TextRecognizer textRecognizer = await EnsureModelIsReady(); + ImageBuffer imageBuffer = ImageBuffer.CreateBufferAttachedToBitmap(bitmap); + RecognizedText recognizedText = textRecognizer.RecognizeTextFromImage(imageBuffer); + + Console.WriteLine(string.Join("\n", recognizedText.Lines.Select(l => l.Text))); + """" + }, + { + ModelType.ImageScaler, """" + using Microsoft.Graphics.Imaging; + using Windows.Graphics.Imaging; + + if (!ImageScaler.IsAvailable()) + { + var op = await ImageScaler.MakeAvailableAsync(); + } + + ImageScaler imageScaler = await ImageScaler.CreateAsync(); + SoftwareBitmap finalImage = imageScaler.ScaleSoftwareBitmap(softwareBitmap, targetWidth, targetHeight); + """" + }, + { + ModelType.BackgroundRemover, """" + using Microsoft.Graphics.Imaging; + using Windows.Graphics.Imaging; + + if (!ImageObjectExtractor.IsAvailable()) + { + var op = await ImageObjectExtractor.MakeAvailableAsync(); + } + + ImageObjectExtractor imageObjectExtractor = await ImageObjectExtractor.CreateWithSoftwareBitmapAsync(softwareBitmap); + + ImageObjectExtractorHint hint = new ImageObjectExtractorHint{ + includeRects: null, + includePoints: + new List { new PointInt32(306, 212), + new PointInt32(216, 336)}, + excludePoints: null}; + + SoftwareBitmap finalImage = imageObjectExtractor.GetSoftwareBitmapObjectMask(hint); + """" + }, + { + ModelType.ImageDescription, """" + using Microsoft.Graphics.Imaging; + using Microsoft.Windows.AI.Generative; + using Microsoft.Windows.AI.ContentModeration; + using Windows.Graphics.Imaging; + + if (!ImageDescriptionGenerator.IsAvailable()) + { + var op = await ImageDescriptionGenerator.MakeAvailableAsync(); + } + + ImageDescriptionGenerator imageDescriptionGenerator = await ImageDescriptionGenerator.CreateAsync(); + + ImageBuffer inputImage = ImageBuffer.CreateCopyFromBitmap(softwareBitmap); + + ContentFilterOptions filterOptions = new ContentFilterOptions(); + filterOptions.PromptMinSeverityLevelToBlock.ViolentContentSeverity = SeverityLevel.Medium; + filterOptions.ResponseMinSeverityLevelToBlock.ViolentContentSeverity = SeverityLevel.Medium; + + LanguageModelResponse languageModelResponse = await imageDescriptionGenerator.DescribeAsync(inputImage, ImageDescriptionScenario.Caption, filterOptions); + + Console.WriteLine(languageModelResponse.Response); + """" + } + }; +} \ No newline at end of file diff --git a/AIDevGallery/Samples/Definitions/WcrApis/WcrCompatibilityChecker.cs b/AIDevGallery/Samples/Definitions/WcrApis/WcrCompatibilityChecker.cs new file mode 100644 index 00000000..69e6c70a --- /dev/null +++ b/AIDevGallery/Samples/Definitions/WcrApis/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.Samples; +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 diff --git a/AIDevGallery/Samples/Definitions/WcrApis/apis.json b/AIDevGallery/Samples/Definitions/WcrApis/apis.json new file mode 100644 index 00000000..408e494c --- /dev/null +++ b/AIDevGallery/Samples/Definitions/WcrApis/apis.json @@ -0,0 +1,59 @@ +{ + "WCRAPIs": { + "Id": "813b4497-a9b2-445a-832a-5dd8bc6d6158", + "Name": "Windows Copilot Runtime APIs", + "Icon": "\uF4A5", + "Apis": { + "PhiSilica": { + "Id": "9b56e116-c142-4be1-827c-cb023743aca2", + "Name": "Phi Silica", + "Icon": "WCRAPI.svg", + "IconGlyph": "\uE8F2", + "Description": "Generate text.", + "ReadmeUrl": "https://github.com/MicrosoftDocs/windows-ai-docs/blob/docs/docs/apis/phi-silica.md", + "License": "ms-pl", + "SampleIdToShowInDocs": "21f2c4a5-3d8e-4b7a-9c0f-6d2e5f3b1c8d" + }, + "TextRecognitionOCR": { + "Id": "4bcc0137-0e9a-4eda-8096-b235fcb0e98b", + "Name": "Text Recognition (OCR)", + "Icon": "WCRAPI.svg", + "IconGlyph": "\uE7A8", + "Description": "Recognize and extract text from an image.", + "ReadmeUrl": "https://github.com/MicrosoftDocs/windows-ai-docs/blob/docs/docs/apis/text-recognition.md", + "License": "ms-pl", + "SampleIdToShowInDocs": "4bcc0137-0e9a-4eda-8096-b235fcb0e98b" + }, + "ImageScaler": { + "Id": "97ed0b95-3f14-415c-bb1f-9a6c59b78c3d", + "Name": "Image Super Resolution", + "Icon": "WCRAPI.svg", + "IconGlyph": "\uED61", + "Description": "Scales an image to a specified size.", + "ReadmeUrl": "https://github.com/MicrosoftDocs/windows-ai-docs/blob/docs/docs/apis/imaging.md", + "License": "ms-pl", + "SampleIdToShowInDocs": "f1e235d1-f1c9-41c7-b489-7e4f95e54668" + }, + "BackgroundRemover": { + "Id": "9d445930-b353-429a-9674-777eef771ccf", + "Name": "Image Segmentation", + "Icon": "WCRAPI.svg", + "IconGlyph": "\uE7C5", + "Description": "Remove the background from an image.", + "ReadmeUrl": "https://github.com/MicrosoftDocs/windows-ai-docs/blob/docs/docs/apis/imaging.md", + "License": "ms-pl", + "SampleIdToShowInDocs": "79eca6f0-3092-4b6f-9a81-94a2aff22559" + }, + "ImageDescription": { + "Id": "bdab049c-9b01-48f4-b12d-acb911b0a61c", + "Name": "Image Description", + "Icon": "WCRAPI.svg", + "IconGlyph": "\uE799", + "Description": "Generate a description of an image.", + "ReadmeUrl": "https://github.com/MicrosoftDocs/windows-ai-docs/blob/docs/docs/apis/imaging.md", + "License": "ms-pl", + "SampleIdToShowInDocs": "a1b1f64f-bc57-41a3-8fb3-ac8f1536d757" + } + } + } +} diff --git a/AIDevGallery/Samples/ModelsDefinitions/apis.json b/AIDevGallery/Samples/ModelsDefinitions/apis.json deleted file mode 100644 index 0f530c14..00000000 --- a/AIDevGallery/Samples/ModelsDefinitions/apis.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml b/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml index 803acbfb..7b5c365e 100644 --- a/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml +++ b/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml @@ -5,7 +5,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml.cs b/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml.cs index 4dfca78c..cbec3f44 100644 --- a/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml.cs +++ b/AIDevGallery/Samples/Open Source Models/Embeddings/RetrievalAugmentedGeneration.xaml.cs @@ -27,13 +27,12 @@ namespace AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings; [GallerySample( Name = "Retrieval Augmented Generation", - Model1Types = [ModelType.LanguageModels], + Model1Types = [ModelType.LanguageModels, ModelType.PhiSilica], Model2Types = [ModelType.EmbeddingModel], Scenario = ScenarioType.TextRetrievalAugmentedGeneration, SharedCode = [ SharedCodeEnum.EmbeddingGenerator, SharedCodeEnum.EmbeddingModelInput, - SharedCodeEnum.GenAIModel, SharedCodeEnum.TokenizerExtensions, SharedCodeEnum.DeviceUtils, SharedCodeEnum.StringData @@ -42,7 +41,6 @@ namespace AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings; "PdfPig", "Microsoft.ML.Tokenizers", "System.Numerics.Tensors", - "Microsoft.ML.OnnxRuntimeGenAI.DirectML", "Microsoft.ML.OnnxRuntime.DirectML", "Microsoft.Extensions.AI.Abstractions", "Microsoft.SemanticKernel.Connectors.InMemory" @@ -51,8 +49,8 @@ namespace AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings; Icon = "\uE8D4")] internal sealed partial class RetrievalAugmentedGeneration : BaseSamplePage { - private readonly ChatOptions _chatOptions = GenAIModel.GetDefaultChatOptions(); private EmbeddingGenerator? _embeddings; + private int _maxTokens = 2048; private IChatClient? _chatClient; private IVectorStore? _vectorStore; private IVectorStoreRecordCollection? _pdfPages; @@ -88,7 +86,6 @@ protected override async Task LoadModelAsync(MultiModelSampleNavigationParameter { _embeddings = new EmbeddingGenerator(sampleParams.ModelPaths[1], sampleParams.HardwareAccelerators[1]); _chatClient = await sampleParams.GetIChatClientAsync(); - _chatOptions.MaxOutputTokens = 2048; sampleParams.NotifyCompletion(); @@ -237,6 +234,8 @@ await _pdfPages.UpsertAsync( ChatGrid.Visibility = Visibility.Visible; SelectNewPDFButton.Visibility = Visibility.Visible; }); + _cts?.Dispose(); + _cts = null; } private async Task DoRAG() @@ -259,8 +258,8 @@ private async Task DoRAG() SearchTextBox.IsEnabled = false; _cts = new CancellationTokenSource(); - const string systemPrompt = "You are a knowledgeable assistant specialized in answering questions based solely on information from specific PDF pages provided by the user. " + - "When responding, focus on delivering clear, accurate answers drawn only from the content in these pages, avoiding outside information or assumptions."; + const string systemPrompt = "You are a knowledgeable assistant specialized in answering questions based solely on information from the following pages. " + + "When responding, focus on delivering clear, accurate answers drawn only from the content in these pages, avoiding outside information or assumptions.\n"; var searchPrompt = this.SearchTextBox.Text; @@ -296,11 +295,10 @@ await Task.Run( { await foreach (var partialResult in _chatClient.GetStreamingResponseAsync( [ - new ChatMessage(ChatRole.System, systemPrompt), - .. pagesChunks.Select(c => new ChatMessage(ChatRole.User, c)), + new ChatMessage(ChatRole.System, systemPrompt + string.Join("\n", pagesChunks)), new ChatMessage(ChatRole.User, searchPrompt), ], - _chatOptions, + new() { MaxOutputTokens = _maxTokens }, _cts.Token)) { fullResult += partialResult; diff --git a/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSearch.xaml b/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSearch.xaml index ec53e128..8f3b6434 100644 --- a/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSearch.xaml +++ b/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSearch.xaml @@ -5,9 +5,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSuggest.xaml b/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSuggest.xaml index 67a0ac61..bd8d1011 100644 --- a/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSuggest.xaml +++ b/AIDevGallery/Samples/Open Source Models/Embeddings/SemanticSuggest.xaml @@ -5,7 +5,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.SentenceEmbeddings.Embeddings" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:shared="using:AIDevGallery.Samples.SharedCode" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml index 7c4db1f7..6d7a1293 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml @@ -4,7 +4,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.ESRGAN" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:samples="using:AIDevGallery.Samples" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml.cs b/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml.cs index f4ed134e..1262ea6c 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml.cs +++ b/AIDevGallery/Samples/Open Source Models/Image Models/ESRGAN/SuperResolution.xaml.cs @@ -19,7 +19,7 @@ namespace AIDevGallery.Samples.OpenSourceModels.ESRGAN; [GallerySample( Model1Types = [ModelType.ESRGAN], - Scenario = ScenarioType.ImageEnhanceImage, + Scenario = ScenarioType.ImageIncreaseFidelity, SharedCode = [ SharedCodeEnum.Prediction, SharedCodeEnum.BitmapFunctions, diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/FFNet/SegmentStreets.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/FFNet/SegmentStreets.xaml index d05be8bc..ab7fc3e9 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/FFNet/SegmentStreets.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/FFNet/SegmentStreets.xaml @@ -4,7 +4,6 @@ x:Class="AIDevGallery.Samples.OpenSourceModels.FFNet.SegmentStreets" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.FFNet" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml index c583ecc3..dc4ce67b 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml @@ -5,7 +5,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.ObjectDetection.FasterRCNN" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/HRNetPose/PoseDetection.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/HRNetPose/PoseDetection.xaml index cc28418c..87b6e93f 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/HRNetPose/PoseDetection.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/HRNetPose/PoseDetection.xaml @@ -4,7 +4,6 @@ x:Class="AIDevGallery.Samples.OpenSourceModels.HRNetPose.PoseDetection" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.HRNetPose" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/ImageNet/ImageClassification.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/ImageNet/ImageClassification.xaml index aee0473f..ecb756e7 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/ImageNet/ImageClassification.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/ImageNet/ImageClassification.xaml @@ -5,7 +5,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/MultiHRNetPose/Multipose.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/MultiHRNetPose/Multipose.xaml index 246fcfcb..c1f20f54 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/MultiHRNetPose/Multipose.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/MultiHRNetPose/Multipose.xaml @@ -4,7 +4,6 @@ x:Class="AIDevGallery.Samples.OpenSourceModels.MultiHRNetPose.Multipose" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.MultiHRNetPose" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml b/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml index 492dc276..f8b876c9 100644 --- a/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml +++ b/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml @@ -4,7 +4,6 @@ x:Class="AIDevGallery.Samples.OpenSourceModels.YOLOv4.YOLOObjectionDetection" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.YOLOv4" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Language Models/Chat.xaml.cs b/AIDevGallery/Samples/Open Source Models/Language Models/Chat.xaml.cs index 0e6440e6..8b170b94 100644 --- a/AIDevGallery/Samples/Open Source Models/Language Models/Chat.xaml.cs +++ b/AIDevGallery/Samples/Open Source Models/Language Models/Chat.xaml.cs @@ -19,21 +19,17 @@ namespace AIDevGallery.Samples.OpenSourceModels.LanguageModels; [GallerySample( Name = "Chat", - Model1Types = [ModelType.LanguageModels], + Model1Types = [ModelType.LanguageModels, ModelType.PhiSilica], Id = "feb39ede-cb55-4e36-9ec6-cf7c5333254f", Icon = "\uE8D4", Scenario = ScenarioType.TextChat, NugetPackageReferences = [ "CommunityToolkit.Mvvm", - "Microsoft.ML.OnnxRuntimeGenAI.DirectML", "Microsoft.Extensions.AI.Abstractions" ], SharedCode = [ - SharedCodeEnum.GenAIModel, SharedCodeEnum.Message, - SharedCodeEnum.LlmPromptTemplate, SharedCodeEnum.ChatTemplateSelector, - SharedCodeEnum.Utils ])] internal sealed partial class Chat : BaseSamplePage { diff --git a/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml b/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml index ef9175c2..a858eccd 100644 --- a/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml +++ b/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml @@ -5,7 +5,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="using:AIDevGallery.Samples.OpenSourceModels.LanguageModels" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml.cs b/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml.cs index c5829201..53ffc2d1 100644 --- a/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml.cs +++ b/AIDevGallery/Samples/Open Source Models/Language Models/ContentModeration.xaml.cs @@ -15,15 +15,12 @@ namespace AIDevGallery.Samples.OpenSourceModels.LanguageModels; [GallerySample( Name = "Content Moderation", - Model1Types = [ModelType.LanguageModels], + Model1Types = [ModelType.LanguageModels, ModelType.PhiSilica], Scenario = ScenarioType.TextContentModeration, NugetPackageReferences = [ - "Microsoft.ML.OnnxRuntimeGenAI.DirectML", "Microsoft.Extensions.AI.Abstractions" ], - SharedCode = [ - SharedCodeEnum.GenAIModel - ], + SharedCode = [], Id = "language-content-moderation", Icon = "\uE8D4")] internal sealed partial class ContentModeration : BaseSamplePage diff --git a/AIDevGallery/Samples/Open Source Models/Language Models/CustomSystemPrompt.xaml b/AIDevGallery/Samples/Open Source Models/Language Models/CustomSystemPrompt.xaml index 54277435..c780fa42 100644 --- a/AIDevGallery/Samples/Open Source Models/Language Models/CustomSystemPrompt.xaml +++ b/AIDevGallery/Samples/Open Source Models/Language Models/CustomSystemPrompt.xaml @@ -22,140 +22,127 @@ - - - - - - + + + + + + + diff --git a/AIDevGallery/Samples/WCRAPIs/PhiSilicaBasic.xaml.cs b/AIDevGallery/Samples/WCRAPIs/PhiSilicaBasic.xaml.cs new file mode 100644 index 00000000..7162916c --- /dev/null +++ b/AIDevGallery/Samples/WCRAPIs/PhiSilicaBasic.xaml.cs @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.Windows.AI.Generative; +using System; +using System.Threading.Tasks; + +namespace AIDevGallery.Samples.WCRAPIs; +[GallerySample( + Name = "Generate with Phi Silica", + Model1Types = [ModelType.PhiSilica], + Id = "21f2c4a5-3d8e-4b7a-9c0f-6d2e5f3b1c8d", + Scenario = ScenarioType.TextGenerateText, + SharedCode = [SharedCodeEnum.WcrModelDownloaderCs, SharedCodeEnum.WcrModelDownloaderXaml], + NugetPackageReferences = [ + "Microsoft.Extensions.AI.Abstractions" + ], + Icon = "\uEE6F")] +internal sealed partial class PhiSilicaBasic : BaseSamplePage +{ + private const int MaxLength = 1000; + private bool _isProgressVisible; + private LanguageModel? _languageModel; + + public PhiSilicaBasic() + { + this.Unloaded += (s, e) => CleanUp(); + this.Loaded += (s, e) => Page_Loaded(); // + this.InitializeComponent(); + } + + protected override Task LoadModelAsync(SampleNavigationParameters sampleParams) + { + if (!LanguageModel.IsAvailable()) + { + WcrModelDownloader.State = WcrApiDownloadState.NotStarted; + _ = WcrModelDownloader.SetDownloadOperation(ModelType.PhiSilica, sampleParams.SampleId, LanguageModel.MakeAvailableAsync); // + } + + // + else + { + _ = GenerateText(InputTextBox.Text); + } + + // + sampleParams.NotifyCompletion(); + return Task.CompletedTask; + } + + private async void WcrModelDownloader_DownloadClicked(object sender, EventArgs e) + { + var operation = LanguageModel.MakeAvailableAsync(); + + var success = await WcrModelDownloader.SetDownloadOperation(operation); + + // + if (success) + { + _ = GenerateText(InputTextBox.Text); + } + + // + } + + // + private void Page_Loaded() + { + InputTextBox.Focus(FocusState.Programmatic); + } + + // + private void CleanUp() + { + CancelGeneration(); + _languageModel?.Dispose(); + } + + public bool IsProgressVisible + { + get => _isProgressVisible; + set + { + _isProgressVisible = value; + DispatcherQueue.TryEnqueue(() => + { + OutputProgressBar.Visibility = value ? Visibility.Visible : Visibility.Collapsed; + StopIcon.Visibility = value ? Visibility.Collapsed : Visibility.Visible; + }); + } + } + + public async Task GenerateText(string prompt) + { + GenerateTextBlock.Text = string.Empty; + GenerateButton.Visibility = Visibility.Collapsed; + StopBtn.Visibility = Visibility.Visible; + IsProgressVisible = true; + InputTextBox.IsEnabled = false; + var contentStartedBeingGenerated = false; // + NarratorHelper.Announce(InputTextBox, "Generating content, please wait.", "GenerateTextWaitAnnouncementActivityId"); // + SendSampleInteractedEvent("GenerateText"); // + + IsProgressVisible = true; + + _languageModel ??= await LanguageModel.CreateAsync(); + + var operation = _languageModel.GenerateResponseWithProgressAsync(prompt); + operation.Progress = (asyncInfo, delta) => + { + DispatcherQueue.TryEnqueue(() => + { + // + if (!contentStartedBeingGenerated) + { + NarratorHelper.Announce(InputTextBox, "Content has started generating.", "GeneratedAnnouncementActivityId"); + contentStartedBeingGenerated = true; + } + + // + if (_isProgressVisible) + { + StopBtn.Visibility = Visibility.Visible; + IsProgressVisible = false; + } + + GenerateTextBlock.Text = asyncInfo.GetResults().Response; + }); + }; + + var result = await operation; + + NarratorHelper.Announce(InputTextBox, "Content has finished generating.", "GenerateDoneAnnouncementActivityId"); // + StopBtn.Visibility = Visibility.Collapsed; + GenerateButton.Visibility = Visibility.Visible; + InputTextBox.IsEnabled = true; + } + + private void GenerateButton_Click(object sender, RoutedEventArgs e) + { + if (this.InputTextBox.Text.Length > 0) + { + _ = GenerateText(InputTextBox.Text); + } + } + + private void TextBox_KeyUp(object sender, KeyRoutedEventArgs e) + { + if (e.Key == Windows.System.VirtualKey.Enter && sender is TextBox) + { + if (InputTextBox.Text.Length > 0) + { + _ = GenerateText(InputTextBox.Text); + } + } + } + + private void CancelGeneration() + { + StopBtn.Visibility = Visibility.Collapsed; + IsProgressVisible = false; + GenerateButton.Visibility = Visibility.Visible; + InputTextBox.IsEnabled = true; + } + + private void StopBtn_Click(object sender, RoutedEventArgs e) + { + CancelGeneration(); + } + + private void InputBox_Changed(object sender, TextChangedEventArgs e) + { + var inputLength = InputTextBox.Text.Length; + if (inputLength > 0) + { + if (inputLength >= MaxLength) + { + InputTextBox.Description = $"{inputLength} of {MaxLength}. Max characters reached."; + } + else + { + InputTextBox.Description = $"{inputLength} of {MaxLength}"; + } + + GenerateButton.IsEnabled = inputLength <= MaxLength; + } + else + { + InputTextBox.Description = string.Empty; + GenerateButton.IsEnabled = false; + } + } +} \ No newline at end of file diff --git a/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml b/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml new file mode 100644 index 00000000..c56db5fc --- /dev/null +++ b/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml.cs b/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml.cs new file mode 100644 index 00000000..bd88cabf --- /dev/null +++ b/AIDevGallery/Samples/WCRAPIs/TextRecognition.xaml.cs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using AIDevGallery.Samples.Attributes; +using AIDevGallery.Samples.SharedCode; +using Microsoft.Graphics.Imaging; +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.Vision; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; +using Windows.Foundation; +using Windows.Graphics.Imaging; +using Windows.Storage; +using Windows.Storage.Pickers; +using Windows.Storage.Streams; + +namespace AIDevGallery.Samples.WCRAPIs; + +[GallerySample( + Name = "Select Text From Image", + Model1Types = [ModelType.TextRecognitionOCR], + Scenario = ScenarioType.ImageRecognizeText, + Id = "4bcc0137-0e9a-4eda-8096-b235fcb0e98b", + SharedCode = [ + SharedCodeEnum.WcrModelDownloaderCs, + SharedCodeEnum.WcrModelDownloaderXaml + ], + AssetFilenames = [ + "OCR.png" + ], + Icon = "\uEE6F")] +internal sealed partial class TextRecognition : BaseSamplePage +{ + private TextRecognizer? _textRecognizer; + private string _recognizedTextString = string.Empty; + + public TextRecognition() + { + this.InitializeComponent(); + } + + protected override async Task LoadModelAsync(SampleNavigationParameters sampleParams) + { + if (!TextRecognizer.IsAvailable()) + { + WcrModelDownloader.State = WcrApiDownloadState.NotStarted; + _ = WcrModelDownloader.SetDownloadOperation(ModelType.TextRecognitionOCR, sampleParams.SampleId, TextRecognizer.MakeAvailableAsync); // + } + + await SetImage(System.IO.Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "OCR.png")); + 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) + { + SendSampleInteractedEvent("LoadImageClicked"); + var window = new Window(); + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window); + + var picker = new FileOpenPicker(); + + WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd); + + picker.FileTypeFilter.Add(".png"); + picker.FileTypeFilter.Add(".jpeg"); + picker.FileTypeFilter.Add(".jpg"); + + picker.ViewMode = PickerViewMode.Thumbnail; + + var file = await picker.PickSingleFileAsync(); + if (file != null) + { + using var stream = await file.OpenReadAsync(); + await SetImage(stream); + } + } + + private async void PasteImage_Click(object sender, RoutedEventArgs e) + { + SendSampleInteractedEvent("PasteImageClick"); + var package = Clipboard.GetContent(); + if (package.Contains(StandardDataFormats.Bitmap)) + { + RectCanvas.Visibility = Visibility.Collapsed; + var streamRef = await package.GetBitmapAsync(); + + IRandomAccessStream stream = await streamRef.OpenReadAsync(); + await SetImage(stream); + } + else if (package.Contains(StandardDataFormats.StorageItems)) + { + var storageItems = await package.GetStorageItemsAsync(); + if (IsImageFile(storageItems[0].Path)) + { + try + { + var storageFile = await StorageFile.GetFileFromPathAsync(storageItems[0].Path); + using var stream = await storageFile.OpenReadAsync(); + await SetImage(stream); + } + catch + { + Console.WriteLine("Invalid Image File"); + } + } + } + } + + private static bool IsImageFile(string fileName) + { + string[] imageExtensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif"]; + return imageExtensions.Contains(System.IO.Path.GetExtension(fileName)?.ToLowerInvariant()); + } + + private async Task SetImage(string filePath) + { + if (File.Exists(filePath)) + { + StorageFile file = await StorageFile.GetFileFromPathAsync(filePath); + using IRandomAccessStream stream = await file.OpenReadAsync(); + await SetImage(stream); + } + } + + private async Task SetImage(IRandomAccessStream stream) + { + var decoder = await BitmapDecoder.CreateAsync(stream); + SoftwareBitmap inputBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); + + if (inputBitmap == null) + { + return; + } + + RectCanvas.Visibility = Visibility.Collapsed; + ViewToggle.Visibility = Visibility.Collapsed; + RectCanvas.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + RectCanvas.Arrange(new Rect(new Point(0, 0), RectCanvas.DesiredSize)); + + var bitmapSource = new SoftwareBitmapSource(); + + // This conversion ensures that the image is Bgra8 and Premultiplied + SoftwareBitmap convertedImage = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied); + await bitmapSource.SetBitmapAsync(convertedImage); + RectCanvas.Children.Clear(); + ImageSrc.Source = bitmapSource; + await RecognizeAndAddTextAsync(convertedImage); + } + + private async Task RecognizeAndAddTextAsync(SoftwareBitmap bitmap) + { + CopyTextButton.Visibility = Visibility.Collapsed; + RectCanvas.Visibility = Visibility.Collapsed; + using var imageBuffer = ImageBuffer.CreateBufferAttachedToBitmap(bitmap); + _textRecognizer ??= await TextRecognizer.CreateAsync(); + RecognizedText? result = _textRecognizer?.RecognizeTextFromImage(imageBuffer, new TextRecognizerOptions()); + + if (result == null) + { + return; + } + + RenderRecognizedText(result); + + CopyTextButton.Visibility = Visibility.Visible; + RectCanvas.Visibility = Visibility.Visible; + } + + private void CopyTextToClipboard(string text) + { + DataPackage dataPackage = new(); + dataPackage.SetText(text); + Clipboard.SetContent(dataPackage); + } + + private void CopyText_Click(object sender, RoutedEventArgs e) + { + CopyTextToClipboard(_recognizedTextString); + } + + private void RenderRecognizedText(RecognizedText recognizedText) + { + RectCanvas.Visibility = Visibility.Visible; + ViewToggle.Visibility = Visibility.Visible; + + List lines = new List(); + + foreach (var line in recognizedText.Lines) + { + lines.Add(line.Text); + + SolidColorBrush backgroundBrush = new SolidColorBrush + { + Color = Colors.Black, + Opacity = .6 + }; + + Grid grid = new Grid + { + Background = backgroundBrush, + CornerRadius = new CornerRadius(8), + Padding = new Thickness(4, 3, 4, 4) + }; + + TextBlock block = new TextBlock + { + IsTextSelectionEnabled = true, + Foreground = new SolidColorBrush(Colors.White), + Text = line.Text, + FontSize = Math.Abs((int)line.BoundingBox.TopRight.Y - (int)line.BoundingBox.BottomRight.Y) * .85, + }; + + grid.Children.Add(block); + RectCanvas.Children.Add(grid); + Canvas.SetLeft(grid, line.BoundingBox.TopLeft.X); + Canvas.SetTop(grid, line.BoundingBox.TopLeft.Y); + } + + _recognizedTextString = string.Join('\n', lines); + } + + private void ViewToggle_Click(object sender, RoutedEventArgs e) + { + RectCanvas.Visibility = RectCanvas.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; + } +} \ No newline at end of file diff --git a/AIDevGallery/Samples/scenarios.json b/AIDevGallery/Samples/scenarios.json index ba42760f..77ef141d 100644 --- a/AIDevGallery/Samples/scenarios.json +++ b/AIDevGallery/Samples/scenarios.json @@ -136,6 +136,21 @@ "Id": "detect-objects", "Icon": "\uE7C5" }, + "RecognizeText": { + "Name": "Recognize Text", + "Description": "Detect text in an image. Upload an image to see recognized text overlaid on the original image.", + "Id": "recognize-text" + }, + "IncreaseFidelity": { + "Name": "Enhance Image", + "Description": "Increase image fidelity with super resolution models. Upload an image from the file system to increase its resolution.", + "Id": "increase-fidelity" + }, + "BackgroundRemover": { + "Name": "Remove Background", + "Description": "Remove image backgrounds with an image segmentation model. Upload an image and select what objects in the image you want to include by clicking on locations in the image. Select remove background to get the extracted objects without the background.", + "Id": "background-remover" + }, "DetectPose": { "Name": "Detect Human Pose", "Description": "Model human posture with a pose detection model. Upload an image including a clearly visible person to see pose estimate drawn onto the image.", @@ -146,12 +161,6 @@ "Description": "Model multiple humans' posture with a pose detection model. Upload an image including multiple clearly visible persons to see pose estimates drawn onto the image.", "Id": "detect-poses" }, - "EnhanceImage": { - "Name": "Enhance Image", - "Description": "Improve image quality with an image enhancement model. Upload a low resolution image and a higher resolution version of the same image will be output.", - "Id": "enhance-image", - "Icon": "\uEE71" - }, "SegmentStreet": { "Name": "Segment Street", "Description": "Segment photos of streets into detectable zones with a street segmentation model. Upload a photo of a clearly visible street to have it segmented into color-coded regions.", diff --git a/AIDevGallery/Styles/Button.xaml b/AIDevGallery/Styles/Button.xaml index 9e3324af..ca95fa71 100644 --- a/AIDevGallery/Styles/Button.xaml +++ b/AIDevGallery/Styles/Button.xaml @@ -2,8 +2,7 @@ + xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"> diff --git a/AIDevGallery/Styles/Colors.xaml b/AIDevGallery/Styles/Colors.xaml new file mode 100644 index 00000000..fddad356 --- /dev/null +++ b/AIDevGallery/Styles/Colors.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Telemetry/Events/WcrApiDownloadFailedEvent.cs b/AIDevGallery/Telemetry/Events/WcrApiDownloadFailedEvent.cs new file mode 100644 index 00000000..cb256838 --- /dev/null +++ b/AIDevGallery/Telemetry/Events/WcrApiDownloadFailedEvent.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using Microsoft.Diagnostics.Telemetry; +using Microsoft.Diagnostics.Telemetry.Internal; +using System; +using System.Diagnostics.Tracing; + +namespace AIDevGallery.Telemetry.Events; + +[EventData] +internal class WcrApiDownloadFailedEvent : EventBase +{ + internal WcrApiDownloadFailedEvent(ModelType apiType, string errorMessage, DateTime errorTime) + { + ApiType = apiType.ToString(); + ErrorMessage = errorMessage; + ErrorTime = errorTime; + } + + public string ApiType { get; } + + public DateTime ErrorTime { get; } + + public string ErrorMessage { get; } + + public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage; + + public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings) + { + } + + public static void Log(ModelType apiType, string errorMessage) + { + TelemetryFactory.Get().LogError("WcrApiDownloadFailed_Event", LogLevel.Critical, new WcrApiDownloadFailedEvent(apiType, errorMessage, DateTime.Now)); + } + + public static void Log(ModelType apiType, Exception ex) + { + TelemetryFactory.Get().LogError("WcrApiDownloadFailed_Event", LogLevel.Critical, new WcrApiDownloadFailedEvent(apiType, ex.Message, DateTime.Now)); + } +} \ No newline at end of file diff --git a/AIDevGallery/Telemetry/Events/WcrApiDownloadRequestedEvent.cs b/AIDevGallery/Telemetry/Events/WcrApiDownloadRequestedEvent.cs new file mode 100644 index 00000000..f034e360 --- /dev/null +++ b/AIDevGallery/Telemetry/Events/WcrApiDownloadRequestedEvent.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using Microsoft.Diagnostics.Telemetry; +using Microsoft.Diagnostics.Telemetry.Internal; +using System; +using System.Diagnostics.Tracing; + +namespace AIDevGallery.Telemetry.Events; + +[EventData] +internal class WcrApiDownloadRequestedEvent : EventBase +{ + internal WcrApiDownloadRequestedEvent(ModelType apiType, string sampleId, DateTime startTime) + { + ApiType = apiType.ToString(); + SampleId = sampleId; + StartTime = startTime; + } + + public string ApiType { get; } + + public DateTime StartTime { get; } + + public string SampleId { get; } + + public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage; + + public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings) + { + } + + public static void Log(ModelType apiType, string sampleId) + { + TelemetryFactory.Get().Log("WcrApiDownloadRequested_Event", LogLevel.Critical, new WcrApiDownloadRequestedEvent(apiType, sampleId, DateTime.Now)); + } +} \ No newline at end of file diff --git a/AIDevGallery/Themes/Generic.xaml b/AIDevGallery/Themes/Generic.xaml index 830f6d1e..aba25078 100644 --- a/AIDevGallery/Themes/Generic.xaml +++ b/AIDevGallery/Themes/Generic.xaml @@ -1,2 +1 @@ - - + \ No newline at end of file diff --git a/AIDevGallery/Utils/AppData.cs b/AIDevGallery/Utils/AppData.cs index c4c630fa..b1daae48 100644 --- a/AIDevGallery/Utils/AppData.cs +++ b/AIDevGallery/Utils/AppData.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using AIDevGallery.Telemetry; +using Microsoft.Windows.AI.ContentModeration; +using Microsoft.Windows.AI.Generative; using System; using System.Collections.Generic; using System.IO; @@ -117,4 +119,7 @@ internal class CustomParametersState public float? Temperature { get; set; } public string? UserPrompt { get; set; } public string? SystemPrompt { get; set; } + public LanguageModelSkill? ModelSkill { get; set; } + public SeverityLevel? InputContentModeration { get; set; } + public SeverityLevel? OutputContentModeration { get; set; } } \ No newline at end of file diff --git a/AIDevGallery/Utils/AppUtils.cs b/AIDevGallery/Utils/AppUtils.cs index 879e6afc..d0e31edb 100644 --- a/AIDevGallery/Utils/AppUtils.cs +++ b/AIDevGallery/Utils/AppUtils.cs @@ -11,12 +11,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using Windows.ApplicationModel; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.DXCore; namespace AIDevGallery.Utils; internal static class AppUtils { + private static readonly Guid DXCORE_ADAPTER_ATTRIBUTE_D3D12_GENERIC_ML = new(0xb71b0d41, 0x1088, 0x422f, 0xa2, 0x7c, 0x2, 0x50, 0xb7, 0xd3, 0xa9, 0x88); + private static bool? _hasNpu; + public static string GetAppVersion(int fieldCount = 4) { Package package = Package.Current; @@ -85,6 +92,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 +110,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"; } } @@ -169,6 +180,7 @@ public static StyleDictionary GetCodeHighlightingStyleFromElementTheme(ElementTh { if (theme == ElementTheme.Dark) { + // Adjust DefaultDark Theme to meet contrast accessibility requirements StyleDictionary darkStyles = StyleDictionary.DefaultDark; darkStyles[ScopeName.Comment].Foreground = StyleDictionary.BrightGreen; darkStyles[ScopeName.XmlDocComment].Foreground = StyleDictionary.BrightGreen; @@ -192,4 +204,102 @@ public static StyleDictionary GetCodeHighlightingStyleFromElementTheme(ElementTh return lightStyles; } } + + public static bool HasNpu() + { + if (_hasNpu.HasValue) + { + return _hasNpu.Value; + } + + IDXCoreAdapterFactory adapterFactory; + if (PInvoke.DXCoreCreateAdapterFactory(typeof(IDXCoreAdapterFactory).GUID, out var adapterFactoryObj) != HRESULT.S_OK) + { + throw new InvalidOperationException("Failed to create adapter factory"); + } + + adapterFactory = (IDXCoreAdapterFactory)adapterFactoryObj; + + // First try getting all GENERIC_ML devices, which is the broadest set of adapters + // and includes both GPUs and NPUs; however, running this sample on an older build of + // Windows may not have drivers that report GENERIC_ML. + IDXCoreAdapterList adapterList; + + adapterFactory.CreateAdapterList([DXCORE_ADAPTER_ATTRIBUTE_D3D12_GENERIC_ML], typeof(IDXCoreAdapterList).GUID, out var adapterListObj); + adapterList = (IDXCoreAdapterList)adapterListObj; + + // Fall back to CORE_COMPUTE if GENERIC_ML devices are not available. This is a more restricted + // set of adapters and may filter out some NPUs. + if (adapterList.GetAdapterCount() == 0) + { + adapterFactory.CreateAdapterList( + [PInvoke.DXCORE_ADAPTER_ATTRIBUTE_D3D12_CORE_COMPUTE], + typeof(IDXCoreAdapterList).GUID, + out adapterListObj); + adapterList = (IDXCoreAdapterList)adapterListObj; + } + + if (adapterList.GetAdapterCount() == 0) + { + throw new InvalidOperationException("No compatible adapters found."); + } + + // Sort the adapters by preference, with hardware and high-performance adapters first. + ReadOnlySpan preferences = + [ + DXCoreAdapterPreference.Hardware, + DXCoreAdapterPreference.HighPerformance + ]; + + adapterList.Sort(preferences); + + List adapters = []; + + for (uint i = 0; i < adapterList.GetAdapterCount(); i++) + { + IDXCoreAdapter adapter; + adapterList.GetAdapter(i, typeof(IDXCoreAdapter).GUID, out var adapterObj); + adapter = (IDXCoreAdapter)adapterObj; + + adapter.GetPropertySize( + DXCoreAdapterProperty.DriverDescription, + out var descriptionSize); + + string adapterDescription; + IntPtr buffer = IntPtr.Zero; + try + { + buffer = Marshal.AllocHGlobal((int)descriptionSize); + unsafe + { + adapter.GetProperty( + DXCoreAdapterProperty.DriverDescription, + descriptionSize, + buffer.ToPointer()); + } + + adapterDescription = Marshal.PtrToStringAnsi(buffer) ?? string.Empty; + } + finally + { + Marshal.FreeHGlobal(buffer); + } + + // Remove trailing null terminator written by DXCore. + while (!string.IsNullOrEmpty(adapterDescription) && adapterDescription[^1] == '\0') + { + adapterDescription = adapterDescription[..^1]; + } + + adapters.Add(adapter); + if (adapterDescription.Contains("NPU")) + { + _hasNpu = true; + return true; + } + } + + _hasNpu = false; + return false; + } } \ No newline at end of file diff --git a/AIDevGallery/Utils/NativeMethods.txt b/AIDevGallery/Utils/NativeMethods.txt new file mode 100644 index 00000000..b26ca639 --- /dev/null +++ b/AIDevGallery/Utils/NativeMethods.txt @@ -0,0 +1,15 @@ +// AppUtils.cs +IDXCoreAdapterFactory +IDXCoreAdapter +IDXCoreAdapterList +D3D_FEATURE_LEVEL +DXCoreCreateAdapterFactory +S_OK +IDMLDevice +ID3D12CommandQueue +ID3D12Device +D3D12CreateDevice +DMLCreateDevice +D3D12_FEATURE_DATA_FEATURE_LEVELS +D3D12_COMMAND_QUEUE_PRIORITY +DXCORE_ADAPTER_ATTRIBUTE_D3D12_CORE_COMPUTE \ No newline at end of file diff --git a/AIDevGallery/Utils/WcrDownloadOperationTracker.cs b/AIDevGallery/Utils/WcrDownloadOperationTracker.cs new file mode 100644 index 00000000..c2d4d7da --- /dev/null +++ b/AIDevGallery/Utils/WcrDownloadOperationTracker.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using Microsoft.Windows.Management.Deployment; +using System.Collections.Generic; +using Windows.Foundation; + +namespace AIDevGallery.Utils; + +internal class WcrDownloadOperationTracker +{ + public static Dictionary> Operations { get; } = new(); +} \ No newline at end of file diff --git a/AIDevGallery/WinAppSDKSelfContainedFix.targets b/AIDevGallery/WinAppSDKSelfContainedFix.targets new file mode 100644 index 00000000..3276599b --- /dev/null +++ b/AIDevGallery/WinAppSDKSelfContainedFix.targets @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + +"; + var sb = new StringBuilder(); + sb.AppendLine(headerF); + + var dllFileFormat = RedirectDlls ? + @" " : + @" "; + + if (!string.IsNullOrEmpty(InAppxManifest)) + { + XmlDocument doc = new XmlDocument(); + doc.Load(InAppxManifest); + var nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("m", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); + + // Add InProcessServer elements to the generated appxmanifest + var xQuery = "./m:Package/m:Extensions/m:Extension/m:InProcessServer"; + var dllFiles = (from di in (new DirectoryInfo(MsixContentDir).EnumerateFiles("*.dll")) select di.Name).ToList(); + foreach (XmlNode winRTFactory in doc.SelectNodes(xQuery, nsmgr)) + { + var dllFileNode = winRTFactory.SelectSingleNode("./m:Path", nsmgr); + var dllFile = dllFileNode.InnerText; + var typesNames = winRTFactory.SelectNodes("./m:ActivatableClass", nsmgr).OfType(); + sb.AppendFormat(dllFileFormat, dllFile); + sb.AppendLine(); + foreach (var typeNode in typesNames) + { + var attribs = typeNode.Attributes.OfType().ToArray(); + var typeName = attribs + .OfType() + .SingleOrDefault(x => x.Name == "ActivatableClassId") + .InnerText; + var xmlEntryFormat = +@" "; + sb.AppendFormat(xmlEntryFormat, typeName); + sb.AppendLine(); + dllFiles.RemoveAll(e => e.Equals(dllFile, StringComparison.OrdinalIgnoreCase)); + } + sb.AppendLine(@" "); + } + if(RedirectDlls) + { + foreach (var dllFile in dllFiles) + { + sb.AppendFormat(dllFileFormat, dllFile); + sb.AppendLine(@""); + } + } + // Add ProxyStub elements to the generated appxmanifest + xQuery = "./m:Package/m:Extensions/m:Extension/m:ProxyStub"; + dllFiles = (from di in (new DirectoryInfo(MsixContentDir).EnumerateFiles("*.dll")) select di.Name).ToList(); + foreach (XmlNode winRTFactory in doc.SelectNodes(xQuery, nsmgr)) + { + var classIDAdded = false; + + var dllFileNode = winRTFactory.SelectSingleNode("./m:Path", nsmgr); + var dllFile = dllFileNode.InnerText; + var typesNamesForProxy = winRTFactory.SelectNodes("./m:Interface", nsmgr).OfType(); + sb.AppendFormat(dllFileFormat, dllFile); + sb.AppendLine(); + foreach (var typeNode in typesNamesForProxy) + { + if(!classIDAdded) + { + var classIdAttribute = winRTFactory.Attributes.OfType().ToArray(); + var classID = classIdAttribute + .OfType() + .SingleOrDefault(x => x.Name == "ClassId") + .InnerText; + + var xmlEntryFormat = @" "; + sb.AppendFormat(xmlEntryFormat, classID); + classIDAdded = true; + } + var attribs = typeNode.Attributes.OfType().ToArray(); + var typeID = attribs + .OfType() + .SingleOrDefault(x => x.Name == "InterfaceId") + .InnerText; + var typeNames = attribs + .OfType() + .SingleOrDefault(x => x.Name == "Name") + .InnerText; + var xmlEntryFormatForStubs = + @" "; + sb.AppendFormat(xmlEntryFormatForStubs, typeNames, typeID); + sb.AppendLine(); + dllFiles.RemoveAll(e => e.Equals(dllFile, StringComparison.OrdinalIgnoreCase)); + } + sb.AppendLine(@" "); + } + if(RedirectDlls) + { + foreach (var dllFile in dllFiles) + { + sb.AppendFormat(dllFileFormat, dllFile); + sb.AppendLine(@""); + } + } + } + sb.AppendLine(@""); + var manifestContent = sb.ToString(); + File.WriteAllText(OutAppManifest, manifestContent, Encoding.UTF8); +]]> + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index e63d4ad2..b5d02696 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -21,10 +21,10 @@ + - diff --git a/README.md b/README.md index c7de4c82..9a4a82ac 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Ensure that the `AIDevGallery` project is set as the startup project in Visual S Press F5 to run AI Dev Gallery! +>**⚠️ Note**: On ARM64-based Copilot+ PCs, make sure to build and run the solution as `ARM64` (and not as `x64`). This is required especially when running the samples that invoke the Windows Copilot Runtime to communicate with models such as Phi Silica. + >**⚠️ Note**: Having issues installing the app on your machine? Let us know by opening an issue and our team will do our best to help!
diff --git a/version.json b/version.json index 9804ca40..738d85b4 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.2.6-alpha", + "version": "0.3.1-alpha", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/rel/v\\d+(?:\\.\\d+)?$"