diff --git a/Samples/AppContentSearch/README.md b/Samples/AppContentSearch/README.md index ba7edfef1..73a132aab 100644 --- a/Samples/AppContentSearch/README.md +++ b/Samples/AppContentSearch/README.md @@ -16,9 +16,17 @@ extendedZipContent: # AppContentSearch Sample Application -This sample demonstrates how to use App Content Search's **AppContentIndex APIs** in a **WinUI3** notes application. It shows how to create, manage, and semantically search through the index that includes both text content and images. It also shows how to use use the search results to enable retrieval augmented genaration (RAG) scenarios with language models. +This sample demonstrates how to use App Content Search's +**AppContentIndex APIs** in a **WinUI3** notes application. +It shows how to create, manage, and semantically search +through the index that includes both text content and images. +It also shows how to use the search results to enable +retrieval augmented generation (RAG) scenarios with language +models. -> **Note**: This sample is targeted and tested for **Windows App SDK 2.0 Experimental2** and **Visual Studio 2022**. The AppContentSearch APIs are experimental and available in Windows App SDK 2.0 experimental2. +> **Note**: This sample is targeted and tested for +> **Windows App SDK 2.0 Preview1** and +> **Visual Studio 2022**. ## Features @@ -42,7 +50,7 @@ This sample demonstrates: ## Building and Running the Sample -* Open the solution file (`AppContentSearch.sln`) in Visual Studio. +* Open the solution file (`NotesApp.sln`) in Visual Studio. * Press Ctrl+Shift+B, or select **Build** \> **Build Solution**. * Run the application to see the Notes app with integrated search functionality. diff --git a/Samples/AppContentSearch/cs-winui/AI/Provider/FoundryAIProvider.cs b/Samples/AppContentSearch/cs-winui/AI/Provider/FoundryAIProvider.cs index f3a449905..8d9ab49c4 100644 --- a/Samples/AppContentSearch/cs-winui/AI/Provider/FoundryAIProvider.cs +++ b/Samples/AppContentSearch/cs-winui/AI/Provider/FoundryAIProvider.cs @@ -1,10 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -using Microsoft.AI.Foundry.Local; -using Microsoft.Extensions.AI; -using Notes.ViewModels; -using OpenAI; -using OpenAI.Chat; using System; using System.ClientModel; using System.Collections.Generic; @@ -13,6 +8,11 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.AI.Foundry.Local; +using Microsoft.Extensions.AI; +using Notes.ViewModels; +using OpenAI; +using OpenAI.Chat; using Windows.Storage; using AppChatRole = Notes.ViewModels.ChatRole; using ExtChatRole = Microsoft.Extensions.AI.ChatRole; @@ -143,7 +143,8 @@ public async IAsyncEnumerable SendStreamingRequestAsync( break; } - if (!hasNext) break; + if (!hasNext) + break; if (cancellationToken.IsCancellationRequested) { @@ -175,7 +176,9 @@ private bool HasCachedModels() { try { +#pragma warning disable CA2000 // Dispose objects before losing scope var foundryManager = new FoundryLocalManager(); +#pragma warning restore CA2000 // Dispose objects before losing scope var cached = foundryManager.ListCachedModelsAsync().GetAwaiter().GetResult(); return cached.Count > 0; } @@ -187,7 +190,8 @@ private bool HasCachedModels() private async Task EnsureFoundryClientAsync(CancellationToken ct) { - if (_foundryClient != null && _foundryChatClient != null) return; + if (_foundryClient != null && _foundryChatClient != null) + return; var settings = ApplicationData.Current.LocalSettings; string alias = (settings.Values[FoundryModelKey] as string)?.Trim() ?? ""; @@ -195,7 +199,9 @@ private async Task EnsureFoundryClientAsync(CancellationToken ct) { try { +#pragma warning disable CA2000 // Dispose objects before losing scope var foundryManager = new FoundryLocalManager(); +#pragma warning restore CA2000 // Dispose objects before losing scope var cached = await foundryManager.ListCachedModelsAsync(); if (cached.Count != 0) @@ -269,4 +275,4 @@ private ChatResponseUpdate Append(SessionEntryViewModel entry, string text, Chat return list; } -} \ No newline at end of file +} diff --git a/Samples/AppContentSearch/cs-winui/MainWindow.xaml.cs b/Samples/AppContentSearch/cs-winui/MainWindow.xaml.cs index 14dce1859..683750318 100644 --- a/Samples/AppContentSearch/cs-winui/MainWindow.xaml.cs +++ b/Samples/AppContentSearch/cs-winui/MainWindow.xaml.cs @@ -2,7 +2,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.Windows.AI.Search.Experimental.AppContentIndex; +using Microsoft.Windows.Search.AppContentIndex; using Notes.Controls; using Notes.Pages; using Notes.ViewModels; @@ -98,10 +98,10 @@ private async Task InitializeAppContentIndexerAsync() GetOrCreateIndexResult? getOrCreateResult = null; await Task.Run(() => { - getOrCreateResult = Microsoft.Windows.AI.Search.Experimental.AppContentIndex.AppContentIndexer.GetOrCreateIndex("NotesIndex"); + getOrCreateResult = AppContentIndexer.GetOrCreateIndex("NotesIndex"); if (getOrCreateResult == null) { - throw new Exception("GetOrCreateIndexResult is null"); + throw new InvalidOperationException("GetOrCreateIndexResult is null"); } if (!getOrCreateResult.Succeeded) { diff --git a/Samples/AppContentSearch/cs-winui/Notes.csproj b/Samples/AppContentSearch/cs-winui/Notes.csproj index d43c76f47..bf3449e29 100644 --- a/Samples/AppContentSearch/cs-winui/Notes.csproj +++ b/Samples/AppContentSearch/cs-winui/Notes.csproj @@ -29,11 +29,6 @@ - - @@ -41,10 +36,6 @@ - - @@ -76,7 +67,7 @@ - + @@ -119,30 +110,6 @@ MSBuild:Compile - diff --git a/Samples/AppContentSearch/cs-winui/Package.appxmanifest b/Samples/AppContentSearch/cs-winui/Package.appxmanifest index c740d6551..32dd743b4 100644 --- a/Samples/AppContentSearch/cs-winui/Package.appxmanifest +++ b/Samples/AppContentSearch/cs-winui/Package.appxmanifest @@ -5,19 +5,17 @@ xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" - xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10" - IgnorableNamespaces="uap rescap systemai"> - + IgnorableNamespaces="uap rescap systemai" + xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"> + - - + Name="ai-powered-notes-winui3-sample" + Publisher="CN=AppContentSearchSample" + Version="1.0.0.0" /> Notes - nikol + AppContentSearchSample Assets\StoreLogo.png diff --git a/Samples/AppContentSearch/cs-winui/Utils/AttachmentProcessor.cs b/Samples/AppContentSearch/cs-winui/Utils/AttachmentProcessor.cs index 8507720fd..b85afe74e 100644 --- a/Samples/AppContentSearch/cs-winui/Utils/AttachmentProcessor.cs +++ b/Samples/AppContentSearch/cs-winui/Utils/AttachmentProcessor.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. -using Microsoft.Windows.AI.Search.Experimental.AppContentIndex; -using Notes.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Windows.Search.AppContentIndex; +using Notes.Models; using Windows.Graphics.Imaging; using Windows.Storage; using Windows.Storage.Streams; @@ -14,7 +14,7 @@ namespace Notes { public static class AttachmentProcessor { - public static EventHandler? AttachmentProcessed; + internal static EventHandler? AttachmentProcessed; private readonly static List _toBeProcessed = new(); private static bool _isProcessing = false; @@ -46,7 +46,7 @@ public async static Task RemoveAttachment(Attachment attachment) { await Task.Run(() => { - MainWindow.AppContentIndexer.Remove(attachment.Id.ToString()); + MainWindow.AppContentIndexer.RemoveContentItem(attachment.Id.ToString()); }); Debug.WriteLine($"Deleted image from index: {attachment.Filename}"); } diff --git a/Samples/AppContentSearch/cs-winui/Utils/Utils.cs b/Samples/AppContentSearch/cs-winui/Utils/Utils.cs index f295a47fa..270c7f334 100644 --- a/Samples/AppContentSearch/cs-winui/Utils/Utils.cs +++ b/Samples/AppContentSearch/cs-winui/Utils/Utils.cs @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. -using Microsoft.Windows.AI.Search.Experimental.AppContentIndex; -using Notes.ViewModels; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.Windows.Search.AppContentIndex; +using Notes.ViewModels; using Windows.Foundation; using Windows.Storage; @@ -149,7 +149,7 @@ await Task.Run(() => AppManagedImageQueryMatch? imageMatch = match as AppManagedImageQueryMatch; if (imageMatch != null) { - Debug.WriteLine($"Image match: {imageMatch.ContentId}, Subregion: {imageMatch.Subregion}"); + Debug.WriteLine($"Image match: {imageMatch.ContentId}, Region of interest: {imageMatch.RegionOfInterest}"); var searchResult = new SearchResult { ContentType = ContentType.Image, @@ -184,18 +184,18 @@ await Task.Run(() => var attachmentsFolder = await GetAttachmentsFolderAsync(); searchResult.Path = attachmentsFolder.Path + "\\" + image.RelativePath; - // Capture bounding box if present (Subregion is IReference) + // Capture bounding box if present (Region of interest is IReference) try { - if (imageMatch.Subregion != null) + if (imageMatch.RegionOfInterest != null) { - var rect = imageMatch.Subregion.Value; + var rect = imageMatch.RegionOfInterest.Value; searchResult.BoundingBox = rect; } } catch (Exception ex) { - Debug.WriteLine("Failed to read image Subregion: " + ex.Message); + Debug.WriteLine("Failed to read image Region of interest: " + ex.Message); } results.Add(searchResult); } @@ -266,4 +266,4 @@ public enum ContentSubType Gif = 3, Bmp = 4 } -} \ No newline at end of file +} diff --git a/Samples/AppContentSearch/cs-winui/ViewModels/NoteViewModel.cs b/Samples/AppContentSearch/cs-winui/ViewModels/NoteViewModel.cs index 4a0e4caaf..04dfd7fe5 100644 --- a/Samples/AppContentSearch/cs-winui/ViewModels/NoteViewModel.cs +++ b/Samples/AppContentSearch/cs-winui/ViewModels/NoteViewModel.cs @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.UI.Dispatching; -using Microsoft.UI.Xaml; -using Microsoft.Windows.AI.Search.Experimental.AppContentIndex; -using Notes.Models; using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.Windows.Search.AppContentIndex; +using Notes.Models; using Windows.Graphics.Imaging; using Windows.Storage; @@ -220,7 +220,7 @@ public async Task RemoveNoteFromIndexAsync() { await Task.Run(() => { - MainWindow.AppContentIndexer.Remove(Note.Id.ToString()); + MainWindow.AppContentIndexer.RemoveContentItem(Note.Id.ToString()); }); Debug.WriteLine($"Deleted note from index: {Note.Filename}"); } @@ -247,7 +247,7 @@ public async static Task ManualDeleteIndex() { await Task.Run(() => { - MainWindow.AppContentIndexer.RemoveAll(); + MainWindow.AppContentIndexer.RemoveAllContentItems(); }); Debug.WriteLine($"Deleted Index"); } @@ -327,7 +327,7 @@ private async Task SaveContentToFileDeleteAndReIndex() await Task.Run(() => { - MainWindow.AppContentIndexer.Remove(Note.Id.ToString()); + MainWindow.AppContentIndexer.RemoveContentItem(Note.Id.ToString()); }); Debug.WriteLine($"Deleted note {Note.Title}"); diff --git a/Samples/AppContentSearch/cs-winui/ViewModels/SearchViewModel.cs b/Samples/AppContentSearch/cs-winui/ViewModels/SearchViewModel.cs index 22bc9ebac..4d74fdfab 100644 --- a/Samples/AppContentSearch/cs-winui/ViewModels/SearchViewModel.cs +++ b/Samples/AppContentSearch/cs-winui/ViewModels/SearchViewModel.cs @@ -1,16 +1,15 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.UI.Xaml; using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; namespace Notes.ViewModels { - public partial class SearchViewModel : ObservableObject + public partial class SearchViewModel : ObservableObject, IDisposable { [ObservableProperty] public bool showResults = false; @@ -27,7 +26,7 @@ public partial class SearchViewModel : ObservableObject public ObservableCollection ImageResults { get; set; } = new(); private string _searchText = string.Empty; - private CancellationTokenSource? _currentSearchCancellation; + private CancellationTokenSource? currentSearchCancellation; public SearchViewModel() { @@ -51,15 +50,15 @@ public void Reset() private async Task Search() { Debug.WriteLine("searching"); - + // Cancel any existing search - _currentSearchCancellation?.Cancel(); - _currentSearchCancellation?.Dispose(); - + currentSearchCancellation?.Cancel(); + currentSearchCancellation?.Dispose(); + // Create new cancellation token for this search - _currentSearchCancellation = new CancellationTokenSource(); - var cancellationToken = _currentSearchCancellation.Token; - + currentSearchCancellation = new CancellationTokenSource(); + var cancellationToken = currentSearchCancellation.Token; + try { Reset(); @@ -123,5 +122,12 @@ private async Task Search() // Handle other exceptions as needed } } + + public void Dispose() + { + currentSearchCancellation?.Cancel(); + currentSearchCancellation?.Dispose(); + currentSearchCancellation = null; + } } }