diff --git a/AIDevGallery.Utils/ModelUrl.cs b/AIDevGallery.Utils/ModelUrl.cs
index 6bd1b536..ed57ce54 100644
--- a/AIDevGallery.Utils/ModelUrl.cs
+++ b/AIDevGallery.Utils/ModelUrl.cs
@@ -104,9 +104,9 @@ public HuggingFaceUrl(string modelNameOrUrl)
modelNameOrUrl = modelNameOrUrl.Trim();
- if (modelNameOrUrl.StartsWith("https://", StringComparison.InvariantCulture))
+ if (modelNameOrUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
- if (!modelNameOrUrl.StartsWith("https://huggingface.co", StringComparison.InvariantCulture))
+ if (!modelNameOrUrl.StartsWith("https://huggingface.co", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid URL", nameof(modelNameOrUrl));
}
@@ -174,7 +174,7 @@ public GitHubUrl(string url)
url = url.Trim();
FullUrl = url;
- if (!url.StartsWith("https://github.com/", StringComparison.InvariantCulture))
+ if (!url.StartsWith("https://github.com/", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid URL", nameof(url));
}
@@ -248,7 +248,11 @@ public static class UrlHelpers
/// The full URL as a string.
public static string GetFullUrl(string url)
{
- if (url.StartsWith("https://github.com", StringComparison.InvariantCulture))
+ if (url.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
+ {
+ return url;
+ }
+ else if (url.StartsWith("local", StringComparison.OrdinalIgnoreCase))
{
return url;
}
diff --git a/AIDevGallery/AIDevGallery.csproj b/AIDevGallery/AIDevGallery.csproj
index 864cb9e8..6633f4a8 100644
--- a/AIDevGallery/AIDevGallery.csproj
+++ b/AIDevGallery/AIDevGallery.csproj
@@ -85,22 +85,16 @@
+
-
-
-
- onnxruntime-genai.dll
- PreserveNewest
- false
-
-
+
+
-
@@ -250,6 +244,7 @@
+
diff --git a/AIDevGallery/Assets/ModelIcons/onnx.svg b/AIDevGallery/Assets/ModelIcons/onnx.svg
new file mode 100644
index 00000000..f57db075
--- /dev/null
+++ b/AIDevGallery/Assets/ModelIcons/onnx.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/AIDevGallery/Controls/ModelSelectionControl.xaml b/AIDevGallery/Controls/ModelSelectionControl.xaml
index 68d78c3c..aa0f9a5b 100644
--- a/AIDevGallery/Controls/ModelSelectionControl.xaml
+++ b/AIDevGallery/Controls/ModelSelectionControl.xaml
@@ -1,6 +1,7 @@
+ Visibility="{x:Bind helpers:ModelDetailsHelper.ShowWhenOnnxModel(ModelDetails)}">
@@ -123,13 +124,15 @@
Source="{x:Bind utils:AppUtils.GetModelSourceImageFromUrl(ModelDetails.Url)}">
-
-
+
-
-
+
+
@@ -216,20 +219,20 @@
Icon="{ui:FontIcon Glyph=}"
Tag="{x:Bind ModelDetails}"
Text="Copy as path"
- Visibility="{x:Bind helpers:ModelDetailsHelper.ShowWhenDownloadedModel(ModelDetails)}" />
+ Visibility="{x:Bind helpers:ModelDetailsHelper.ShowWhenOnnxModel(ModelDetails)}" />
-
+ Visibility="{x:Bind helpers:ModelDetailsHelper.ShowWhenOnnxModel(ModelDetails)}" />
+
+ Visibility="{x:Bind helpers:ModelDetailsHelper.ShowWhenOnnxModel(ModelDetails)}" />
@@ -333,8 +336,7 @@
Source="{x:Bind utils:AppUtils.GetModelSourceImageFromUrl(ModelDetails.Url)}">
-
-
+
@@ -519,8 +521,7 @@
Source="{x:Bind utils:AppUtils.GetModelSourceImageFromUrl(ModelDetails.Url)}">
-
-
+
diff --git a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs
index e6f4ac4e..7ed96fad 100644
--- a/AIDevGallery/Controls/ModelSelectionControl.xaml.cs
+++ b/AIDevGallery/Controls/ModelSelectionControl.xaml.cs
@@ -103,7 +103,7 @@ private void ResetAndLoadModelList(ModelDetails? selectedModel = null)
if (AvailableModels.Count > 0)
{
var modelIds = AvailableModels.Select(s => s.ModelDetails.Id);
- var modelOrApiUsageHistory = App.AppData.UsageHistory.Where(id => modelIds.Contains(id));
+ var modelOrApiUsageHistory = App.AppData.UsageHistoryV2?.FirstOrDefault(u => modelIds.Contains(u.Id));
ModelDetails? modelToPreselect = null;
@@ -112,20 +112,33 @@ private void ResetAndLoadModelList(ModelDetails? selectedModel = null)
modelToPreselect = AvailableModels.Where(m => m.ModelDetails.Id == selectedModel.Id).FirstOrDefault()?.ModelDetails;
}
- if (modelToPreselect != null)
+ if (modelToPreselect == null && modelOrApiUsageHistory != default)
{
- SetSelectedModel(selectedModel);
- }
- else if (modelOrApiUsageHistory.Any())
- {
- // select most recently used if there is one
- var modelId = modelOrApiUsageHistory.First();
- SetSelectedModel(AvailableModels.Where(s => s.ModelDetails.Id == modelId).First().ModelDetails);
+ var models = AvailableModels.Where(am => am.ModelDetails.Id == modelOrApiUsageHistory.Id).ToList();
+ if (models.Count > 0)
+ {
+ if (modelOrApiUsageHistory.HardwareAccelerator != null)
+ {
+ var model = models.FirstOrDefault(m => m.ModelDetails.HardwareAccelerators.Contains(modelOrApiUsageHistory.HardwareAccelerator.Value));
+ if (model != null)
+ {
+ modelToPreselect = model.ModelDetails;
+ }
+ }
+
+ if (modelToPreselect == null)
+ {
+ modelToPreselect = models.FirstOrDefault()?.ModelDetails;
+ }
+ }
}
- else
+
+ if (modelToPreselect == null)
{
- SetSelectedModel(AvailableModels[0].ModelDetails);
+ modelToPreselect = AvailableModels[0].ModelDetails;
}
+
+ SetSelectedModel(modelToPreselect);
}
else
{
@@ -134,7 +147,7 @@ private void ResetAndLoadModelList(ModelDetails? selectedModel = null)
}
}
- private void SetSelectedModel(ModelDetails? modelDetails)
+ private void SetSelectedModel(ModelDetails? modelDetails, HardwareAccelerator? accelerator = null)
{
if (modelDetails != null)
{
@@ -167,7 +180,13 @@ private void SetViewSelection(ModelDetails modelDetails)
if (IsSelectionEnabled)
{
ModelSelectionItemsView.DeselectAll();
- ModelSelectionItemsView.Select(AvailableModels.IndexOf(AvailableModels.First(a => a.ModelDetails.Id == modelDetails.Id)));
+
+ var models = AvailableModels.Where(a => a.ModelDetails == modelDetails).ToList();
+
+ if (models.Count != 0)
+ {
+ ModelSelectionItemsView.Select(AvailableModels.IndexOf(models.First()));
+ }
}
}
diff --git a/AIDevGallery/Helpers/ModelDetailsHelper.cs b/AIDevGallery/Helpers/ModelDetailsHelper.cs
index db0d0f4a..6e05d6f6 100644
--- a/AIDevGallery/Helpers/ModelDetailsHelper.cs
+++ b/AIDevGallery/Helpers/ModelDetailsHelper.cs
@@ -155,11 +155,21 @@ public static Visibility ShowWhenOllama(ModelDetails modelDetails)
return modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.OLLAMA) ? Visibility.Visible : Visibility.Collapsed;
}
- public static Visibility ShowWhenDownloadedModel(ModelDetails modelDetails)
+ private static bool IsOnnxModel(ModelDetails modelDetails)
{
return modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.CPU)
|| modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.DML)
- || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.QNN)
+ || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.QNN);
+ }
+
+ public static Visibility ShowWhenOnnxModel(ModelDetails modelDetails)
+ {
+ return IsOnnxModel(modelDetails) ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public static Visibility ShowWhenDownloadedModel(ModelDetails modelDetails)
+ {
+ return IsOnnxModel(modelDetails) && !modelDetails.IsUserAdded
? Visibility.Visible : Visibility.Collapsed;
}
}
\ No newline at end of file
diff --git a/AIDevGallery/MainWindow.xaml.cs b/AIDevGallery/MainWindow.xaml.cs
index a8e38a13..9e7dabb3 100644
--- a/AIDevGallery/MainWindow.xaml.cs
+++ b/AIDevGallery/MainWindow.xaml.cs
@@ -57,6 +57,10 @@ public void NavigateToPage(object? obj)
{
NavigateToApiOrModelPage(modelTypes[0]);
}
+ else if (obj is ModelDetails)
+ {
+ Navigate("Models", obj);
+ }
else
{
Navigate("Home");
diff --git a/AIDevGallery/Models/BaseSampleNavigationParameters.cs b/AIDevGallery/Models/BaseSampleNavigationParameters.cs
index 5be22b10..650180ad 100644
--- a/AIDevGallery/Models/BaseSampleNavigationParameters.cs
+++ b/AIDevGallery/Models/BaseSampleNavigationParameters.cs
@@ -16,6 +16,7 @@ internal abstract class BaseSampleNavigationParameters(TaskCompletionSource samp
public TaskCompletionSource SampleLoadedCompletionSource { get; set; } = sampleLoadedCompletionSource;
protected abstract string ChatClientModelPath { get; }
+ protected abstract HardwareAccelerator ChatClientHardwareAccelerator { get; }
protected abstract LlmPromptTemplate? ChatClientPromptTemplate { get; }
public void NotifyCompletion()
@@ -35,7 +36,11 @@ public void NotifyCompletion()
return new OllamaChatClient(OllamaHelper.GetOllamaUrl(), modelId);
}
- return await GenAIModel.CreateAsync(ChatClientModelPath, ChatClientPromptTemplate, CancellationToken).ConfigureAwait(false);
+ return await GenAIModel.CreateAsync(
+ ChatClientModelPath,
+ ChatClientPromptTemplate,
+ ChatClientHardwareAccelerator == HardwareAccelerator.QNN ? "qnn" : null,
+ CancellationToken).ConfigureAwait(false);
}
internal abstract void SendSampleInteractionEvent(string? customInfo = null);
diff --git a/AIDevGallery/Models/CachedModel.cs b/AIDevGallery/Models/CachedModel.cs
index f946f26a..bc077955 100644
--- a/AIDevGallery/Models/CachedModel.cs
+++ b/AIDevGallery/Models/CachedModel.cs
@@ -20,11 +20,16 @@ internal class CachedModel
public CachedModel(ModelDetails details, string path, bool isFile, long modelSize)
{
Details = details;
- if (details.Url.StartsWith("https://github.com", StringComparison.InvariantCulture))
+ if (details.Url.StartsWith("https://github.com", StringComparison.OrdinalIgnoreCase))
{
Url = details.Url;
Source = CachedModelSource.GitHub;
}
+ else if (details.Url.StartsWith("local", StringComparison.OrdinalIgnoreCase))
+ {
+ Url = details.Url;
+ Source = CachedModelSource.Local;
+ }
else
{
Url = new HuggingFaceUrl(details.Url).FullUrl;
@@ -42,5 +47,6 @@ public CachedModel(ModelDetails details, string path, bool isFile, long modelSiz
internal enum CachedModelSource
{
GitHub,
- HuggingFace
+ HuggingFace,
+ Local
}
\ No newline at end of file
diff --git a/AIDevGallery/Models/MultiModelSampleNavigationParameters.cs b/AIDevGallery/Models/MultiModelSampleNavigationParameters.cs
index 8a2bed8e..a3cb89d4 100644
--- a/AIDevGallery/Models/MultiModelSampleNavigationParameters.cs
+++ b/AIDevGallery/Models/MultiModelSampleNavigationParameters.cs
@@ -22,6 +22,7 @@ internal class MultiModelSampleNavigationParameters(
public HardwareAccelerator[] HardwareAccelerators { get; } = hardwareAccelerators;
protected override string ChatClientModelPath => ModelPaths[0];
+ protected override HardwareAccelerator ChatClientHardwareAccelerator => HardwareAccelerators[0];
protected override LlmPromptTemplate? ChatClientPromptTemplate => promptTemplates[0];
internal override void SendSampleInteractionEvent(string? customInfo = null)
diff --git a/AIDevGallery/Models/SampleNavigationParameters.cs b/AIDevGallery/Models/SampleNavigationParameters.cs
index cdd4a799..5749fe89 100644
--- a/AIDevGallery/Models/SampleNavigationParameters.cs
+++ b/AIDevGallery/Models/SampleNavigationParameters.cs
@@ -23,6 +23,7 @@ internal class SampleNavigationParameters(
public string SampleId => sampleId;
protected override string ChatClientModelPath => ModelPath;
+ protected override HardwareAccelerator ChatClientHardwareAccelerator => HardwareAccelerator;
protected override LlmPromptTemplate? ChatClientPromptTemplate => promptTemplate;
internal override void SendSampleInteractionEvent(string? customInfo = null)
diff --git a/AIDevGallery/Models/Samples.cs b/AIDevGallery/Models/Samples.cs
index 12eb90ca..a16c48c2 100644
--- a/AIDevGallery/Models/Samples.cs
+++ b/AIDevGallery/Models/Samples.cs
@@ -111,7 +111,7 @@ public string Icon
icon = "GitHub.dark.svg";
}
}
- else if (Url.StartsWith("ollama", StringComparison.InvariantCultureIgnoreCase))
+ else if (Url.StartsWith("ollama", StringComparison.OrdinalIgnoreCase))
{
if (App.Current.RequestedTheme == Microsoft.UI.Xaml.ApplicationTheme.Light)
{
@@ -122,6 +122,10 @@ public string Icon
icon = "ollama.dark.svg";
}
}
+ else if (Url.StartsWith("local", StringComparison.OrdinalIgnoreCase))
+ {
+ icon = "onnx.svg";
+ }
else
{
icon = "HuggingFace.svg";
diff --git a/AIDevGallery/Pages/Models/AddModelPage.xaml b/AIDevGallery/Pages/Models/AddModelPage.xaml
index b2297212..5e78d54d 100644
--- a/AIDevGallery/Pages/Models/AddModelPage.xaml
+++ b/AIDevGallery/Pages/Models/AddModelPage.xaml
@@ -68,6 +68,10 @@
IsEnabled="{Binding ElementName=SearchTextBox, Path=Text, Converter={StaticResource EmptyStringToObjectConverter}}"
Style="{StaticResource AccentButtonStyle}" />
+
p.Dml != null))
- )
+ var pathComponents = config.RFilename.Split("/");
+ string modelPath = string.Empty;
+ if (pathComponents.Length > 1)
{
- var pathComponents = config.RFilename.Split("/");
- string modelPath = string.Empty;
- if (pathComponents.Length > 1)
- {
- modelPath = string.Join("/", pathComponents.Take(pathComponents.Length - 1));
- }
-
- var modelUrl = $"https://huggingface.co/{result.Id}/tree/main/{modelPath}";
+ modelPath = string.Join("/", pathComponents.Take(pathComponents.Length - 1));
+ }
- bool isDmlModel = genAIConfig.Model.Decoder.SessionOptions.ProviderOptions.Any(p => p.Dml != null);
+ var modelUrl = $"https://huggingface.co/{result.Id}/tree/main/{modelPath}";
- var curratedModel = ModelTypeHelpers.ModelDetails.Values.Where(m => m.Url == modelUrl).FirstOrDefault();
+ var curratedModel = ModelTypeHelpers.ModelDetails.Values.Where(m => m.Url == modelUrl).FirstOrDefault();
- var filesToDownload = await ModelInformationHelper.GetDownloadFilesFromHuggingFace(new HuggingFaceUrl(modelUrl));
+ var filesToDownload = await ModelInformationHelper.GetDownloadFilesFromHuggingFace(new HuggingFaceUrl(modelUrl));
- var details = curratedModel ?? new ModelDetails()
- {
- Id = "useradded-languagemodel-" + Guid.NewGuid().ToString(),
- Name = result.Id + " " + (isDmlModel ? "DML" : "CPU"),
- Url = modelUrl,
- Description = "TODO",
- HardwareAccelerators = [isDmlModel ? HardwareAccelerator.DML : HardwareAccelerator.CPU],
- IsUserAdded = true,
- PromptTemplate = GetTemplateFromName(result.Id),
- Size = filesToDownload.Sum(f => f.Size),
- ReadmeUrl = readmeUrl != null ? $"https://huggingface.co/{result.Id}/blob/main/{readmeUrl}" : null
- };
-
- string? licenseKey = null;
- if (result.Tags != null)
+ var details = curratedModel ?? new ModelDetails()
+ {
+ Id = "useradded-languagemodel-" + Guid.NewGuid().ToString(),
+ Name = result.Id + " " + accelerator.ToString(),
+ Url = modelUrl,
+ Description = "Model downloaded from HuggingFace",
+ HardwareAccelerators = [accelerator],
+ IsUserAdded = true,
+ PromptTemplate = GetTemplateFromName(result.Id),
+ Size = filesToDownload.Sum(f => f.Size),
+ ReadmeUrl = readmeUrl != null ? $"https://huggingface.co/{result.Id}/blob/main/{readmeUrl}" : null
+ };
+
+ string? licenseKey = null;
+ if (result.Tags != null)
+ {
+ var licenseTag = result.Tags.Where(t => t.StartsWith("license:", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
+ if (licenseTag != null)
{
- var licenseTag = result.Tags.Where(t => t.StartsWith("license:", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
- if (licenseTag != null)
- {
- licenseKey = licenseTag.Split(":").Last();
- }
+ licenseKey = licenseTag.Split(":").Last();
}
+ }
- if (curratedModel == null)
- {
- details.License = licenseKey;
- }
+ if (curratedModel == null)
+ {
+ details.License = licenseKey;
+ }
- ResultState state = ResultState.NotDownloaded;
+ ResultState state = ResultState.NotDownloaded;
- if (App.ModelCache.IsModelCached(details.Url))
- {
- state = ResultState.Downloaded;
- }
- else if (App.ModelCache.DownloadQueue.GetDownload(details.Url) != null)
- {
- state = ResultState.Downloading;
- }
+ if (App.ModelCache.IsModelCached(details.Url))
+ {
+ state = ResultState.Downloaded;
+ }
+ else if (App.ModelCache.DownloadQueue.GetDownload(details.Url) != null)
+ {
+ state = ResultState.Downloading;
+ }
- DispatcherQueue.TryEnqueue(() =>
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ this.results.Add(new Result
{
- this.results.Add(new Result
- {
- Details = details,
- SearchResult = result,
- License = LicenseInfo.GetLicenseInfo(licenseKey),
- State = state,
- HFUrl = $"https://huggingface.co/{result.Id}"
- });
+ Details = details,
+ SearchResult = result,
+ License = LicenseInfo.GetLicenseInfo(licenseKey),
+ State = state,
+ HFUrl = $"https://huggingface.co/{result.Id}"
});
- }
+ });
if (actionBlock.InputCount == 0)
{
@@ -195,9 +190,9 @@ private async Task SearchModels(string query, CancellationToken cancellationToke
continue;
}
- var configs = result.Siblings.Where(r => r.RFilename.EndsWith("genai_config.json", StringComparison.InvariantCultureIgnoreCase));
+ var configs = result.Siblings.Where(r => r.RFilename.EndsWith("genai_config.json", StringComparison.OrdinalIgnoreCase));
- var readmeSiblings = result.Siblings.Where(r => r.RFilename.EndsWith("readme.md", StringComparison.InvariantCultureIgnoreCase));
+ var readmeSiblings = result.Siblings.Where(r => r.RFilename.EndsWith("readme.md", StringComparison.OrdinalIgnoreCase));
string? readmeUrl = null;
if (readmeSiblings.Any())
@@ -331,6 +326,157 @@ private void SearchTextBox_Loaded(object sender, RoutedEventArgs e)
{
this.Focus(FocusState.Programmatic);
}
+
+ private HardwareAccelerator GetHardwareAcceleratorFromConfig(string configContents)
+ {
+ if (configContents.Contains(""""backend_path": "QnnHtp.dll"""", StringComparison.OrdinalIgnoreCase))
+ {
+ return HardwareAccelerator.QNN;
+ }
+
+ var config = JsonSerializer.Deserialize(configContents, SourceGenerationContext.Default.GenAIConfig);
+ if (config == null)
+ {
+ throw new FileLoadException("genai_config.json is not valid");
+ }
+
+ if (config.Model.Decoder.SessionOptions.ProviderOptions.Any(p => p.Dml != null))
+ {
+ return HardwareAccelerator.DML;
+ }
+
+ return HardwareAccelerator.CPU;
+ }
+
+ private async void AddLocalClicked(object sender, RoutedEventArgs e)
+ {
+ var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
+ var picker = new FolderPicker();
+ picker.FileTypeFilter.Add("*");
+ WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
+ var folder = await picker.PickSingleFolderAsync();
+
+ if (folder != null)
+ {
+ var files = Directory.GetFiles(folder.Path);
+ var config = files.Where(r => Path.GetFileName(r) == "genai_config.json").FirstOrDefault();
+
+ if (string.IsNullOrEmpty(config) || App.ModelCache.Models.Any(m => m.Path == folder.Path))
+ {
+ var message = string.IsNullOrEmpty(config) ?
+ "The folder does not contain a model you can add. Ensure \"genai_config.json\" is present in the selected directory" :
+ "This model is already added";
+
+ ContentDialog confirmFolderDialog = new()
+ {
+ Title = "Can't add model",
+ Content = message,
+ XamlRoot = this.Content.XamlRoot,
+ CloseButtonText = "OK"
+ };
+
+ await confirmFolderDialog.ShowAsync();
+ return;
+ }
+
+ HardwareAccelerator accelerator = HardwareAccelerator.CPU;
+
+ try
+ {
+ string configContents = string.Empty;
+ configContents = await File.ReadAllTextAsync(config);
+ accelerator = GetHardwareAcceleratorFromConfig(configContents);
+ }
+ catch (Exception ex)
+ {
+ ContentDialog confirmFolderDialog = new()
+ {
+ Title = "Can't read genai_config.json",
+ Content = ex.Message,
+ XamlRoot = this.Content.XamlRoot,
+ CloseButtonText = "OK"
+ };
+
+ await confirmFolderDialog.ShowAsync();
+ return;
+ }
+
+ var nameTextBox = new TextBox()
+ {
+ Text = Path.GetFileName(folder.Path),
+ Width = 300,
+ HorizontalAlignment = HorizontalAlignment.Left,
+ Margin = new Thickness(0, 0, 0, 10),
+ Header = "Model name"
+ };
+
+ ContentDialog nameModelDialog = new()
+ {
+ Title = "Add model",
+ Content = new StackPanel()
+ {
+ Orientation = Orientation.Vertical,
+ Spacing = 8,
+ Children =
+ {
+ new TextBlock()
+ {
+ Text = $"Adding ONNX model from \n \"{folder.Path}\"",
+ TextWrapping = TextWrapping.WrapWholeWords
+ },
+ nameTextBox
+ }
+ },
+ XamlRoot = this.Content.XamlRoot,
+ CloseButtonText = "Cancel",
+ PrimaryButtonText = "Add",
+ DefaultButton = ContentDialogButton.Primary,
+ Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style
+ };
+
+ string modelName = nameTextBox.Text;
+
+ nameTextBox.TextChanged += (s, e) =>
+ {
+ if (string.IsNullOrEmpty(nameTextBox.Text))
+ {
+ nameModelDialog.IsPrimaryButtonEnabled = false;
+ }
+ else
+ {
+ modelName = nameTextBox.Text;
+ nameModelDialog.IsPrimaryButtonEnabled = true;
+ }
+ };
+
+ var result = await nameModelDialog.ShowAsync();
+ if (result != ContentDialogResult.Primary)
+ {
+ return;
+ }
+
+ DirectoryInfo dirInfo = new DirectoryInfo(folder.Path);
+ long dirSize = await Task.Run(() => dirInfo.EnumerateFiles("*", SearchOption.AllDirectories).Sum(file => file.Length));
+
+ var details = new ModelDetails()
+ {
+ Id = "useradded-local-languagemodel-" + Guid.NewGuid().ToString(),
+ Name = modelName,
+ Url = $"local-file:///{folder.Path}",
+ Description = "Localy added GenAI Model",
+ HardwareAccelerators = [accelerator],
+ IsUserAdded = true,
+ PromptTemplate = GetTemplateFromName(folder.Path),
+ Size = dirSize,
+ ReadmeUrl = null,
+ License = "unknown"
+ };
+
+ await App.ModelCache.AddLocalModelToCache(details, folder.Path);
+
+ App.MainWindow.NavigateToPage(details);
+ }
+ }
}
internal partial class Result : ObservableObject
diff --git a/AIDevGallery/Pages/Models/ModelPage.xaml b/AIDevGallery/Pages/Models/ModelPage.xaml
index 04c4d3ca..f1891671 100644
--- a/AIDevGallery/Pages/Models/ModelPage.xaml
+++ b/AIDevGallery/Pages/Models/ModelPage.xaml
@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:AIDevGallery.Controls"
+ xmlns:local="using:AIDevGallery.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
@@ -13,6 +14,7 @@
xmlns:utils="using:AIDevGallery.Utils"
mc:Ignorable="d">
+
@@ -98,9 +100,10 @@
FontSize=16}">
models)
{
- var latestModelOrApiUsageHistory = App.AppData.UsageHistory.FirstOrDefault(id => models.Any(m => m.Id == id));
+ var latestModelOrApiUsageHistory = App.AppData.UsageHistoryV2?.FirstOrDefault(u => models.Any(m => m.Id == u.Id));
- if (latestModelOrApiUsageHistory != null)
+ if (latestModelOrApiUsageHistory != default)
{
// select most recently used if there is one
- return models.First(m => m.Id == latestModelOrApiUsageHistory);
+ return models.First(m => m.Id == latestModelOrApiUsageHistory.Id);
}
return models.FirstOrDefault();
@@ -227,7 +227,8 @@ await App.AppData.AddMru(
SubItemId = selectedModelDetails!.Id,
DisplayName = scenario.Name
},
- selectedModelDetails.Id);
+ selectedModelDetails.Id,
+ selectedModelDetails.HardwareAccelerators.First());
}
}
diff --git a/AIDevGallery/Pages/SettingsPage.xaml.cs b/AIDevGallery/Pages/SettingsPage.xaml.cs
index 1c4d8a52..8476c9ac 100644
--- a/AIDevGallery/Pages/SettingsPage.xaml.cs
+++ b/AIDevGallery/Pages/SettingsPage.xaml.cs
@@ -68,7 +68,7 @@ private void GetStorageInfo()
long totalCacheSize = 0;
- foreach (var cachedModel in App.ModelCache.Models.OrderBy(m => m.Details.Name))
+ foreach (var cachedModel in App.ModelCache.Models.Where(m => m.Path.StartsWith(cacheFolderPath, StringComparison.OrdinalIgnoreCase)).OrderBy(m => m.Details.Name))
{
cachedModels.Add(cachedModel);
totalCacheSize += cachedModel.ModelSize;
diff --git a/AIDevGallery/ProjectGenerator/Generator.cs b/AIDevGallery/ProjectGenerator/Generator.cs
index fd6e4029..d4dfc568 100644
--- a/AIDevGallery/ProjectGenerator/Generator.cs
+++ b/AIDevGallery/ProjectGenerator/Generator.cs
@@ -276,45 +276,32 @@ static void AddPackageReference(ProjectItemGroupElement itemGroup, string packag
{
packageReferenceItem.AddMetadata("PrivateAssets", "all", true);
}
- else if (packageName == "Microsoft.AI.DirectML" ||
- packageName == "Microsoft.ML.OnnxRuntime.DirectML" ||
- packageName == "Microsoft.ML.OnnxRuntimeGenAI.DirectML")
+ else if (packageName == "Microsoft.ML.OnnxRuntime.DirectML" ||
+ packageName == "Microsoft.ML.OnnxRuntimeGenAI.DirectML")
{
packageReferenceItem.Condition = "$(Platform) == 'x64'";
}
- else if (packageName == "Microsoft.ML.OnnxRuntime.Qnn" ||
- packageName == "Microsoft.ML.OnnxRuntimeGenAI" ||
- packageName == "Microsoft.ML.OnnxRuntimeGenAI.Managed")
+ else if (packageName == "Microsoft.ML.OnnxRuntime.QNN" ||
+ packageName == "Microsoft.ML.OnnxRuntimeGenAI.QNN" ||
+ packageName == "Microsoft.ML.OnnxRuntimeGenAI")
{
packageReferenceItem.Condition = "$(Platform) == 'ARM64'";
}
var versionStr = PackageVersionHelpers.PackageVersions[packageName];
packageReferenceItem.AddMetadata("Version", versionStr, true);
-
- if (packageName == "Microsoft.ML.OnnxRuntimeGenAI")
- {
- var noneItem = itemGroup.AddItem("None", "$(PKGMicrosoft_ML_OnnxRuntimeGenAI)\\runtimes\\win-arm64\\native\\onnxruntime-genai.dll");
- noneItem.Condition = "$(Platform) == 'ARM64'";
- noneItem.AddMetadata("Link", "onnxruntime-genai.dll", false);
- noneItem.AddMetadata("CopyToOutputDirectory", "PreserveNewest", false);
- noneItem.AddMetadata("Visible", "false", false);
-
- packageReferenceItem.AddMetadata("GeneratePathProperty", "true", true);
- packageReferenceItem.AddMetadata("ExcludeAssets", "all", true);
- }
}
foreach (var packageName in packageReferences)
{
if (packageName == "Microsoft.ML.OnnxRuntime.DirectML")
{
- AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntime.Qnn");
+ AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntime.QNN");
}
else if (packageName == "Microsoft.ML.OnnxRuntimeGenAI.DirectML")
{
- AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntime.Qnn");
- AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntimeGenAI");
+ AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntime.QNN");
+ AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntimeGenAI.QNN");
AddPackageReference(itemGroup, "Microsoft.ML.OnnxRuntimeGenAI.Managed");
}
diff --git a/AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json b/AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json
index 7be048c3..2b01c95c 100644
--- a/AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json
+++ b/AIDevGallery/Samples/Definitions/Models/languagemodels.modelgroup.json
@@ -32,7 +32,6 @@
"Description": "Phi 4 Mini CPU Accuracy Level 4",
"HardwareAccelerator": "CPU",
"Size": 4930563630,
- "SupportedOnQualcomm": false,
"Icon": "Microsoft.svg",
"ParameterSize": "3.8B",
"PromptTemplate": "Phi3",
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 4ce5047f..c9d322c6 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
@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.IO;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
@@ -26,6 +27,9 @@ namespace AIDevGallery.Samples.OpenSourceModels.ESRGAN;
SharedCodeEnum.NarratorHelper,
SharedCodeEnum.DeviceUtils,
],
+ AssetFilenames = [
+ "Enhance.png"
+ ],
NugetPackageReferences = [
"System.Drawing.Common",
"Microsoft.ML.OnnxRuntime.DirectML",
@@ -56,7 +60,10 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
{
var hardwareAccelerator = sampleParams.HardwareAccelerator;
await InitModel(sampleParams.ModelPath, hardwareAccelerator);
+
sampleParams.NotifyCompletion();
+
+ await EnhanceImage(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "Enhance.png"));
}
private Task InitModel(string modelPath, HardwareAccelerator hardwareAccelerator)
diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml.cs b/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml.cs
index a9a569c5..889e17aa 100644
--- a/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml.cs
+++ b/AIDevGallery/Samples/Open Source Models/Image Models/Faster RCNN/ObjectDetection.xaml.cs
@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
@@ -54,7 +55,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
sampleParams.NotifyCompletion();
// Loads inference on default image
- await DetectObjects(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\pose_default.png");
+ await DetectObjects(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "pose_default.png"));
}
//
diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/SINet/DetectBackground.xaml.cs b/AIDevGallery/Samples/Open Source Models/Image Models/SINet/DetectBackground.xaml.cs
index 88e1f2d4..f4ed2959 100644
--- a/AIDevGallery/Samples/Open Source Models/Image Models/SINet/DetectBackground.xaml.cs
+++ b/AIDevGallery/Samples/Open Source Models/Image Models/SINet/DetectBackground.xaml.cs
@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.IO;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
@@ -65,7 +66,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
sampleParams.NotifyCompletion();
- await Detect(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\detection_default.png");
+ await Detect(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "detection_default.png"));
}
private Task InitModel(string modelPath, HardwareAccelerator hardwareAccelerator)
diff --git a/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml.cs b/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml.cs
index 5301c39d..c0245e0b 100644
--- a/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml.cs
+++ b/AIDevGallery/Samples/Open Source Models/Image Models/YOLOv4/YOLOObjectionDetection.xaml.cs
@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.IO;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
@@ -65,7 +66,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
sampleParams.NotifyCompletion();
// Loads inference on default image
- await DetectObjects(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\team.jpg");
+ await DetectObjects(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "team.jpg"));
}
private Task InitModel(string modelPath, HardwareAccelerator hardwareAccelerator)
diff --git a/AIDevGallery/Samples/Open Source Models/Multimodal Models/DescribeImage.xaml.cs b/AIDevGallery/Samples/Open Source Models/Multimodal Models/DescribeImage.xaml.cs
index fc234183..3d55d2f3 100644
--- a/AIDevGallery/Samples/Open Source Models/Multimodal Models/DescribeImage.xaml.cs
+++ b/AIDevGallery/Samples/Open Source Models/Multimodal Models/DescribeImage.xaml.cs
@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -51,12 +52,15 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
await InitModel(sampleParams.ModelPath, sampleParams.CancellationToken);
sampleParams.NotifyCompletion();
+ //
// Load default image
if (!sampleParams.CancellationToken.IsCancellationRequested)
{
- imageFile = await StorageFile.GetFileFromPathAsync(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\team.jpg");
+ imageFile = await StorageFile.GetFileFromPathAsync(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "team.jpg"));
LoadImage(this.imageFile);
}
+
+ //
}
private async Task InitModel(string modelPath, CancellationToken ct)
diff --git a/AIDevGallery/Samples/SharedCode/IChatClient/GenAIModel.cs b/AIDevGallery/Samples/SharedCode/IChatClient/GenAIModel.cs
index 934b1d87..ae2a37f7 100644
--- a/AIDevGallery/Samples/SharedCode/IChatClient/GenAIModel.cs
+++ b/AIDevGallery/Samples/SharedCode/IChatClient/GenAIModel.cs
@@ -31,6 +31,7 @@ internal class GenAIModel : IChatClient
private LlmPromptTemplate? _template;
private static readonly SemaphoreSlim _createSemaphore = new(1, 1);
private static OgaHandle? _ogaHandle;
+ private Config? _config;
private static ChatOptions GetDefaultChatOptions()
{
@@ -53,7 +54,7 @@ private GenAIModel(string modelDir)
_metadata = new ChatClientMetadata("GenAIChatClient", new Uri($"file:///{modelDir}"));
}
- public static async Task CreateAsync(string modelDir, LlmPromptTemplate? template = null, CancellationToken cancellationToken = default)
+ public static async Task CreateAsync(string modelDir, LlmPromptTemplate? template = null, string? provider = null, CancellationToken cancellationToken = default)
{
#pragma warning disable CA2000 // Dispose objects before losing scope
var model = new GenAIModel(modelDir);
@@ -66,7 +67,7 @@ private GenAIModel(string modelDir)
await _createSemaphore.WaitAsync(cancellationToken);
lockAcquired = true;
cancellationToken.ThrowIfCancellationRequested();
- await model.InitializeAsync(modelDir, cancellationToken);
+ await model.InitializeAsync(modelDir, provider, cancellationToken);
}
catch
{
@@ -98,6 +99,7 @@ public void Dispose()
_model?.Dispose();
_tokenizer?.Dispose();
_ogaHandle?.Dispose();
+ _config?.Dispose();
}
private string GetPrompt(IEnumerable history)
@@ -263,12 +265,18 @@ void TransferMetadataValue(string propertyName, object defaultValue)
}
}
- private Task InitializeAsync(string modelDir, CancellationToken cancellationToken = default)
+ private Task InitializeAsync(string modelDir, string? provider = null, CancellationToken cancellationToken = default)
{
return Task.Run(
() =>
{
- _model = new Model(modelDir);
+ _config = new Config(modelDir);
+ if (!string.IsNullOrEmpty(provider))
+ {
+ _config.AppendProvider(provider);
+ }
+
+ _model = new Model(_config);
cancellationToken.ThrowIfCancellationRequested();
_tokenizer = new Tokenizer(_model);
},
diff --git a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs
index 4e67148b..e810d16d 100644
--- a/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs
+++ b/AIDevGallery/Samples/WCRAPIs/BackgroundRemover.xaml.cs
@@ -66,7 +66,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
private async Task LoadDefaultImage()
{
- var file = await StorageFile.GetFileFromPathAsync(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\pose_default.png");
+ var file = await StorageFile.GetFileFromPathAsync(System.IO.Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "pose_default.png"));
using var stream = await file.OpenReadAsync();
await SetImage(stream);
}
diff --git a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs
index 5e297a36..6d2ada44 100644
--- a/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs
+++ b/AIDevGallery/Samples/WCRAPIs/ImageDescription.xaml.cs
@@ -71,7 +71,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
private async Task LoadDefaultImage()
{
- var file = await StorageFile.GetFileFromPathAsync(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\Road.png");
+ var file = await StorageFile.GetFileFromPathAsync(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "Road.png"));
using var stream = await file.OpenReadAsync();
await SetImage(stream);
}
diff --git a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs
index fbdbf478..38a6cc6d 100644
--- a/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs
+++ b/AIDevGallery/Samples/WCRAPIs/IncreaseFidelity.xaml.cs
@@ -10,6 +10,7 @@
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Windows.Management.Deployment;
using System;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
@@ -59,7 +60,7 @@ protected override async Task LoadModelAsync(SampleNavigationParameters samplePa
private async Task LoadDefaultImage()
{
- var file = await StorageFile.GetFileFromPathAsync(Windows.ApplicationModel.Package.Current.InstalledLocation.Path + "\\Assets\\Enhance.png");
+ var file = await StorageFile.GetFileFromPathAsync(Path.Join(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets", "Enhance.png"));
using var stream = await file.OpenReadAsync();
await SetImage(stream);
}
diff --git a/AIDevGallery/Utils/AppData.cs b/AIDevGallery/Utils/AppData.cs
index b1daae48..0dc55a2d 100644
--- a/AIDevGallery/Utils/AppData.cs
+++ b/AIDevGallery/Utils/AppData.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using AIDevGallery.Models;
using AIDevGallery.Telemetry;
using Microsoft.Windows.AI.ContentModeration;
using Microsoft.Windows.AI.Generative;
@@ -20,8 +21,7 @@ internal class AppData
public required LinkedList MostRecentlyUsedItems { get; set; }
public CustomParametersState? LastCustomParamtersState { get; set; }
- // model or api ids
- public required LinkedList UsageHistory { get; set; }
+ public LinkedList? UsageHistoryV2 { get; set; }
public bool IsDiagnosticDataEnabled { get; set; }
@@ -73,8 +73,10 @@ public async Task SaveAsync()
await File.WriteAllTextAsync(GetConfigFilePath(), str);
}
- public async Task AddMru(MostRecentlyUsedItem item, string? modelOrApiId = null)
+ public async Task AddMru(MostRecentlyUsedItem item, string? modelOrApiId = null, HardwareAccelerator? hardwareAccelerator = null)
{
+ UsageHistoryV2 ??= new LinkedList();
+
foreach (var toRemove in MostRecentlyUsedItems.Where(i => i.ItemId == item.ItemId).ToArray())
{
MostRecentlyUsedItems.Remove(toRemove);
@@ -87,8 +89,13 @@ public async Task AddMru(MostRecentlyUsedItem item, string? modelOrApiId = null)
if (!string.IsNullOrWhiteSpace(modelOrApiId))
{
- UsageHistory.Remove(modelOrApiId);
- UsageHistory.AddFirst(modelOrApiId);
+ var existingItem = UsageHistoryV2.Where(u => u.Id == modelOrApiId).FirstOrDefault();
+ if (existingItem != default)
+ {
+ UsageHistoryV2.Remove(existingItem);
+ }
+
+ UsageHistoryV2.AddFirst(new UsageHistory(modelOrApiId, hardwareAccelerator));
}
MostRecentlyUsedItems.AddFirst(item);
@@ -104,7 +111,7 @@ private static AppData GetDefault()
{
ModelCachePath = cacheDir,
MostRecentlyUsedItems = new(),
- UsageHistory = new()
+ UsageHistoryV2 = new()
};
}
}
@@ -122,4 +129,6 @@ internal class CustomParametersState
public LanguageModelSkill? ModelSkill { get; set; }
public SeverityLevel? InputContentModeration { get; set; }
public SeverityLevel? OutputContentModeration { get; set; }
-}
\ No newline at end of file
+}
+
+internal record UsageHistory(string Id, HardwareAccelerator? HardwareAccelerator);
\ No newline at end of file
diff --git a/AIDevGallery/Utils/AppUtils.cs b/AIDevGallery/Utils/AppUtils.cs
index d3efb156..c99326e2 100644
--- a/AIDevGallery/Utils/AppUtils.cs
+++ b/AIDevGallery/Utils/AppUtils.cs
@@ -169,16 +169,21 @@ public static string GetHardwareAcceleratorDescription(HardwareAccelerator hardw
}
}
- public static string GetModelSourceNameFromUrl(string url)
+ public static string GetModelSourceOriginFromUrl(string url)
{
- if (url.StartsWith("https://huggingface.co", StringComparison.InvariantCultureIgnoreCase))
+ if (url.StartsWith("https://huggingface.co", StringComparison.OrdinalIgnoreCase))
{
- return "Hugging Face";
+ return "This model was downloaded from Hugging Face";
}
- if (url.StartsWith("https://github.co", StringComparison.InvariantCultureIgnoreCase))
+ if (url.StartsWith("https://github.co", StringComparison.OrdinalIgnoreCase))
{
- return "GitHub";
+ return "This model was downloaded from GitHub";
+ }
+
+ if (url.StartsWith("local", StringComparison.OrdinalIgnoreCase))
+ {
+ return "This model was added by you";
}
return string.Empty;
@@ -213,7 +218,7 @@ public static Uri GetLicenseUrlFromModel(ModelDetails model)
public static ImageSource GetModelSourceImageFromUrl(string url)
{
- if (url.StartsWith("https://github", StringComparison.InvariantCultureIgnoreCase))
+ if (url.StartsWith("https://github", StringComparison.OrdinalIgnoreCase))
{
if (App.Current.RequestedTheme == Microsoft.UI.Xaml.ApplicationTheme.Light)
{
@@ -224,7 +229,7 @@ public static ImageSource GetModelSourceImageFromUrl(string url)
return new SvgImageSource(new Uri("ms-appx:///Assets/ModelIcons/GitHub.dark.svg"));
}
}
- else if (url.StartsWith("ollama", StringComparison.InvariantCultureIgnoreCase))
+ else if (url.StartsWith("ollama", StringComparison.OrdinalIgnoreCase))
{
if (App.Current.RequestedTheme == Microsoft.UI.Xaml.ApplicationTheme.Light)
{
@@ -235,6 +240,10 @@ public static ImageSource GetModelSourceImageFromUrl(string url)
return new SvgImageSource(new Uri("ms-appx:///Assets/ModelIcons/ollama.dark.svg"));
}
}
+ else if (url.StartsWith("local", StringComparison.OrdinalIgnoreCase))
+ {
+ return new SvgImageSource(new Uri("ms-appx:///Assets/ModelIcons/onnx.svg"));
+ }
else
{
return new SvgImageSource(new Uri("ms-appx:///Assets/ModelIcons/HuggingFace.svg"));
diff --git a/AIDevGallery/Utils/ModelCache.cs b/AIDevGallery/Utils/ModelCache.cs
index e66a4748..2ec01019 100644
--- a/AIDevGallery/Utils/ModelCache.cs
+++ b/AIDevGallery/Utils/ModelCache.cs
@@ -82,6 +82,13 @@ public async Task SetCacheFolderPath(string newPath, List? models =
return download;
}
+ public async Task AddLocalModelToCache(ModelDetails modelDetails, string modelPath, bool isFile = false)
+ {
+ var cachedModel = new CachedModel(modelDetails, modelPath, isFile, modelDetails.Size);
+ await CacheStore.AddModel(cachedModel);
+ return cachedModel;
+ }
+
public CachedModel? GetCachedModel(string url)
{
url = UrlHelpers.GetFullUrl(url);
@@ -112,6 +119,13 @@ public async Task DeleteModelFromCache(CachedModel model)
{
ModelDeletedEvent.Log(model.Url);
await CacheStore.RemoveModel(model);
+
+ if (model.Url.StartsWith("local", System.StringComparison.OrdinalIgnoreCase))
+ {
+ // do not delete models added by user that are not in the cache folder
+ return;
+ }
+
if (model.IsFile && File.Exists(model.Path))
{
File.Delete(model.Path);
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 73630693..117f428d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,6 @@
-
@@ -22,12 +21,13 @@
-
+
-
-
-
+
+
+
+