Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Samples/AppContentSearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down
22 changes: 12 additions & 10 deletions Samples/AppContentSearch/cs-winui/AI/Provider/FoundryAIProvider.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -143,7 +143,8 @@ public async IAsyncEnumerable<ChatResponseUpdate> SendStreamingRequestAsync(
break;
}

if (!hasNext) break;
if (!hasNext)
break;

if (cancellationToken.IsCancellationRequested)
{
Expand Down Expand Up @@ -175,7 +176,7 @@ private bool HasCachedModels()
{
try
{
var foundryManager = new FoundryLocalManager();
using var foundryManager = new FoundryLocalManager();
var cached = foundryManager.ListCachedModelsAsync().GetAwaiter().GetResult();
return cached.Count > 0;
}
Expand All @@ -187,15 +188,16 @@ 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() ?? "";
if (string.IsNullOrWhiteSpace(alias))
{
try
{
var foundryManager = new FoundryLocalManager();
using var foundryManager = new FoundryLocalManager();

var cached = await foundryManager.ListCachedModelsAsync();
if (cached.Count != 0)
Expand Down Expand Up @@ -269,4 +271,4 @@ private ChatResponseUpdate Append(SessionEntryViewModel entry, string text, Chat

return list;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Notes.Models;
using Notes.ViewModels;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
Expand Down Expand Up @@ -58,8 +59,26 @@ public async Task UpdateAttachment(AttachmentViewModel attachment, string? attac
_boundingBox = boundingBox;

AttachmentVM = attachment;

string? filename = attachment.Attachment.Filename;
if (string.IsNullOrWhiteSpace(filename))
{
Debug.WriteLine($"AttachmentView: attachment id={attachment.Attachment.Id} has no filename; skipping.");
AttachmentImage.Source = null;
return;
}

StorageFolder attachmentsFolder = await Utils.GetAttachmentsFolderAsync();
StorageFile attachmentFile = await attachmentsFolder.GetFileAsync(attachment.Attachment.Filename);

// Use TryGetItemAsync so a missing/stale file (e.g. deleted on disk but still in the
// search index) does not throw FileNotFoundException and crash the app.
StorageFile? attachmentFile = await attachmentsFolder.TryGetItemAsync(filename) as StorageFile;
if (attachmentFile == null)
{
Debug.WriteLine($"AttachmentView: file '{filename}' not found in attachments folder; skipping.");
AttachmentImage.Source = null;
return;
}

switch (AttachmentVM.Attachment.Type)
{
Expand Down
17 changes: 16 additions & 1 deletion Samples/AppContentSearch/cs-winui/Controls/SearchView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@
</ItemsView.Layout>
</ItemsView>
</StackPanel>

<!-- OCR Text Results List -->
<StackPanel Visibility="{x:Bind ViewModel.ShowOcrResults, Mode=OneWay}">
<TextBlock x:Name="OcrResultsTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Margin="4,12">OCR Text Results</TextBlock>

<ItemsView x:Name="resultsOcrItemsView"
ItemsSource="{x:Bind ViewModel.OcrResults}"
ItemInvoked="ResultsItemsView_ItemInvoked"
IsItemInvokedEnabled="True"
SelectionMode="None"
ItemTemplate="{StaticResource TextSearchResultDataTemplate}">
</ItemsView>
</StackPanel>


<!-- Text Results List -->
Expand Down Expand Up @@ -127,7 +141,8 @@
Height="32"
Width="400"
PlaceholderText="Search for anything..."
QuerySubmitted="SearchBox_QuerySubmitted">
QuerySubmitted="SearchBox_QuerySubmitted"
TextChanged="SearchBox_TextChanged">
<AutoSuggestBox.QueryIcon>
<FontIcon
x:Name="SearchBoxQueryIcon"
Expand Down
35 changes: 34 additions & 1 deletion Samples/AppContentSearch/cs-winui/Controls/SearchView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public sealed partial class SearchView : UserControl
public SearchView()
{
this.InitializeComponent();
this.Unloaded += (_, _) => ViewModel.Dispose();
}

private void HideResultsPanel()
Expand All @@ -31,7 +32,6 @@ private void HideResultsPanel()

private async void ResultsItemsView_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs e)
{
var context = await AppDataContext.GetCurrentAsync();
var item = e.InvokedItem as SearchResult;

if (MainWindow.Instance != null && item != null)
Expand All @@ -40,8 +40,24 @@ private async void ResultsItemsView_ItemInvoked(ItemsView sender, ItemsViewItemI
{
await MainWindow.Instance.SelectNoteById(item.SourceId);
}
else if (item.ContentType == ContentType.OcrText)
{
if (item.AttachmentId is int attachmentId)
{
await MainWindow.Instance.SelectNoteById(
item.SourceId,
attachmentId,
item.MostRelevantSentence,
item.BoundingBox);
}
else
{
await MainWindow.Instance.SelectNoteById(item.SourceId);
}
}
else
{
var context = await AppDataContext.GetCurrentAsync();
var attachment = context.Attachments.Where(a => a.Id == item.SourceId).FirstOrDefault();

if (attachment != null)
Expand Down Expand Up @@ -138,6 +154,23 @@ private void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuery
ViewModel.HandleQuerySubmitted(sender.Text);
}

private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
// Only react to user input, not programmatic changes
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
ViewModel.HandleTextChanged(sender.Text);
}
}

public void InitializeQuerySessions()
{
if (MainWindow.AppContentIndexer != null)
{
ViewModel.InitializeQuerySessions(MainWindow.AppContentIndexer, DispatcherQueue);
}
}

private void ItemContainer_BringIntoViewRequested(UIElement sender, BringIntoViewRequestedEventArgs args)
{
// When the popup is being hidden we get a spurious BringIntoView request which the Repeater doesn't handle well.
Expand Down
Loading