diff --git a/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs b/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs index 749b0fd7..18f0ed96 100644 --- a/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs +++ b/AIDevGallery.SourceGenerator/Models/HardwareAccelerator.cs @@ -14,6 +14,7 @@ internal enum HardwareAccelerator WCRAPI, OLLAMA, OPENAI, + MINIMAX, NPU, GPU, VitisAI, diff --git a/AIDevGallery.Tests/IntegrationTests/MiniMaxIntegrationTests.cs b/AIDevGallery.Tests/IntegrationTests/MiniMaxIntegrationTests.cs new file mode 100644 index 00000000..133c7c73 --- /dev/null +++ b/AIDevGallery.Tests/IntegrationTests/MiniMaxIntegrationTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.ExternalModelUtils; +using Microsoft.Extensions.AI; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading.Tasks; + +namespace AIDevGallery.Tests.IntegrationTests; + +[TestClass] +[TestCategory("Integration")] +public class MiniMaxIntegrationTests +{ + private static string? GetApiKey() + { + return Environment.GetEnvironmentVariable("MINIMAX_API_KEY"); + } + + [TestMethod] + public async Task GetModelsAsync_WithValidApiKey_ReturnsModels() + { + var apiKey = GetApiKey(); + if (string.IsNullOrEmpty(apiKey)) + { + Assert.Inconclusive("MINIMAX_API_KEY environment variable not set. Skipping integration test."); + return; + } + + // Set the key temporarily + var originalKey = MiniMaxModelProvider.MiniMaxKey; + try + { + MiniMaxModelProvider.MiniMaxKey = apiKey; + + var models = await MiniMaxModelProvider.Instance.GetModelsAsync(); + Assert.IsNotNull(models); + + var modelList = new System.Collections.Generic.List(models); + Assert.IsTrue(modelList.Count > 0, "Should return at least one model"); + } + finally + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + } + + [TestMethod] + public async Task GetIChatClient_WithValidApiKey_CanSendMessage() + { + var apiKey = GetApiKey(); + if (string.IsNullOrEmpty(apiKey)) + { + Assert.Inconclusive("MINIMAX_API_KEY environment variable not set. Skipping integration test."); + return; + } + + var originalKey = MiniMaxModelProvider.MiniMaxKey; + try + { + MiniMaxModelProvider.MiniMaxKey = apiKey; + + var client = MiniMaxModelProvider.Instance.GetIChatClient("minimax://MiniMax-M2.5-highspeed"); + Assert.IsNotNull(client, "Should create a chat client with valid API key"); + + var response = await client.GetResponseAsync("Say hello in one word."); + Assert.IsNotNull(response); + Assert.IsTrue(response.Text.Length > 0, "Response should contain text"); + } + finally + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + } + + [TestMethod] + public async Task GetIChatClient_WithValidApiKey_SupportsStreaming() + { + var apiKey = GetApiKey(); + if (string.IsNullOrEmpty(apiKey)) + { + Assert.Inconclusive("MINIMAX_API_KEY environment variable not set. Skipping integration test."); + return; + } + + var originalKey = MiniMaxModelProvider.MiniMaxKey; + try + { + MiniMaxModelProvider.MiniMaxKey = apiKey; + + var client = MiniMaxModelProvider.Instance.GetIChatClient("minimax://MiniMax-M2.5-highspeed"); + Assert.IsNotNull(client, "Should create a chat client with valid API key"); + + var chunks = new System.Text.StringBuilder(); + await foreach (var update in client.GetStreamingResponseAsync("Say hello in one word.")) + { + if (update.Text != null) + { + chunks.Append(update.Text); + } + } + + Assert.IsTrue(chunks.Length > 0, "Streaming response should contain text"); + } + finally + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + } +} diff --git a/AIDevGallery.Tests/UnitTests/ExternalModelUtils/MiniMaxModelProviderTests.cs b/AIDevGallery.Tests/UnitTests/ExternalModelUtils/MiniMaxModelProviderTests.cs new file mode 100644 index 00000000..00bd027b --- /dev/null +++ b/AIDevGallery.Tests/UnitTests/ExternalModelUtils/MiniMaxModelProviderTests.cs @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.ExternalModelUtils; +using AIDevGallery.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System.Threading.Tasks; + +namespace AIDevGallery.Tests.UnitTests; + +[TestClass] +public class MiniMaxModelProviderTests +{ + [TestMethod] + public void BasicPropertiesReturnExpectedValues() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + + // Assert - Test all basic properties together + Assert.AreEqual("MiniMax", provider.Name); + Assert.AreEqual(HardwareAccelerator.MINIMAX, provider.ModelHardwareAccelerator); + Assert.AreEqual("minimax://", provider.UrlPrefix); + Assert.AreEqual("https://api.minimax.io/v1", provider.Url); + Assert.AreEqual("The model will run on the cloud via MiniMax", provider.ProviderDescription); + Assert.AreEqual("OpenAI", provider.IChatClientImplementationNamespace); + } + + [TestMethod] + public void NugetPackageReferencesContainsRequiredPackages() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + + // Act + var packages = provider.NugetPackageReferences; + + // Assert + Assert.IsNotNull(packages); + Assert.AreEqual(1, packages.Count, "Should contain exactly 1 package"); + Assert.IsTrue(packages.Contains("Microsoft.Extensions.AI.OpenAI")); + } + + [TestMethod] + public void GetDetailsUrlReturnsDocumentationPage() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var modelDetails = new ModelDetails { Name = "MiniMax-M2.7" }; + + // Act + var url = provider.GetDetailsUrl(modelDetails); + + // Assert + Assert.IsNotNull(url); + Assert.AreEqual("https://platform.minimaxi.com/document/Models", url); + } + + [TestMethod] + public void InstanceIsSingleton() + { + // Arrange & Act + var instance1 = MiniMaxModelProvider.Instance; + var instance2 = MiniMaxModelProvider.Instance; + + // Assert + Assert.AreSame(instance1, instance2, "Instance should be a singleton"); + } + + [TestMethod] + public async Task GetModelsAsyncWithoutApiKeyReturnsEmpty() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var originalKey = MiniMaxModelProvider.MiniMaxKey; + + try + { + // Ensure no API key is set + MiniMaxModelProvider.MiniMaxKey = null; + + // Act + var models = await provider.GetModelsAsync(); + + // Assert + Assert.IsNotNull(models); + Assert.IsFalse(models.Any(), "Should return empty when no API key is set"); + } + finally + { + // Restore original key + if (originalKey != null) + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + } + } + + [TestMethod] + public async Task GetModelsAsyncWithApiKeyReturnsKnownModels() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var originalKey = MiniMaxModelProvider.MiniMaxKey; + + try + { + // Set a test API key + MiniMaxModelProvider.MiniMaxKey = "test-api-key"; + + // Act + var models = await provider.GetModelsAsync(); + var modelList = models.ToList(); + + // Assert + Assert.IsNotNull(modelList); + Assert.AreEqual(3, modelList.Count, "Should return 3 known MiniMax models"); + + // Verify model IDs and URL prefixes + Assert.IsTrue(modelList.Any(m => m.Name == "MiniMax-M2.7"), "Should include M2.7"); + Assert.IsTrue(modelList.Any(m => m.Name == "MiniMax-M2.5"), "Should include M2.5"); + Assert.IsTrue(modelList.Any(m => m.Name == "MiniMax-M2.5-highspeed"), "Should include M2.5-highspeed"); + + // Verify all models use the minimax URL prefix + foreach (var model in modelList) + { + Assert.IsTrue(model.Url.StartsWith("minimax://"), $"Model URL should start with minimax://, got: {model.Url}"); + Assert.IsTrue(model.Id.StartsWith("minimax-"), $"Model ID should start with minimax-, got: {model.Id}"); + Assert.AreEqual(HardwareAccelerator.MINIMAX, model.HardwareAccelerators.First()); + } + } + finally + { + // Restore original key + if (originalKey != null) + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + else + { + MiniMaxModelProvider.MiniMaxKey = null; + } + } + } + + [TestMethod] + public void GetIChatClientWithoutApiKeyReturnsNull() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var originalKey = MiniMaxModelProvider.MiniMaxKey; + + try + { + MiniMaxModelProvider.MiniMaxKey = null; + + // Act + var client = provider.GetIChatClient("minimax://MiniMax-M2.7"); + + // Assert + Assert.IsNull(client, "Should return null when no API key is set"); + } + finally + { + if (originalKey != null) + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + } + } + + [TestMethod] + public void GetIChatClientWithEmptyModelIdReturnsNull() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + + // Act + var client = provider.GetIChatClient("minimax://"); + + // Assert + Assert.IsNull(client, "Should return null for empty model ID"); + } + + [TestMethod] + public void GetIChatClientStringReturnsValidCodeSnippet() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var url = "minimax://MiniMax-M2.7"; + + // Act + var result = provider.GetIChatClientString(url); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("OpenAIClient"), "Code snippet should reference OpenAIClient"); + Assert.IsTrue(result.Contains("api.minimax.io/v1"), "Code snippet should reference MiniMax API URL"); + Assert.IsTrue(result.Contains("MiniMax-M2.7"), "Code snippet should reference the model ID"); + Assert.IsTrue(result.Contains("AsIChatClient"), "Code snippet should include AsIChatClient()"); + } + + [TestMethod] + public void ClearCachedModelsDoesNotThrow() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + + // Act & Assert - Should not throw + provider.ClearCachedModels(); + } + + [TestMethod] + public async Task GetModelsAsyncWithIgnoreCachedReturnsConsistentResults() + { + // Arrange + var provider = MiniMaxModelProvider.Instance; + var originalKey = MiniMaxModelProvider.MiniMaxKey; + + try + { + MiniMaxModelProvider.MiniMaxKey = "test-api-key"; + + // Act + var models1 = await provider.GetModelsAsync(ignoreCached: false); + var models2 = await provider.GetModelsAsync(ignoreCached: true); + + // Assert + Assert.IsNotNull(models1); + Assert.IsNotNull(models2); + Assert.AreEqual(models1.Count(), models2.Count(), "Both calls should return same number of models"); + } + finally + { + if (originalKey != null) + { + MiniMaxModelProvider.MiniMaxKey = originalKey; + } + else + { + MiniMaxModelProvider.MiniMaxKey = null; + } + } + } +} diff --git a/AIDevGallery/AIDevGallery.csproj b/AIDevGallery/AIDevGallery.csproj index faeaca95..a676cbe6 100644 --- a/AIDevGallery/AIDevGallery.csproj +++ b/AIDevGallery/AIDevGallery.csproj @@ -263,6 +263,11 @@ + + + + + diff --git a/AIDevGallery/Assets/ModelIcons/MiniMax.dark.png b/AIDevGallery/Assets/ModelIcons/MiniMax.dark.png new file mode 100644 index 00000000..f7e9fb19 Binary files /dev/null and b/AIDevGallery/Assets/ModelIcons/MiniMax.dark.png differ diff --git a/AIDevGallery/Assets/ModelIcons/MiniMax.dark.svg b/AIDevGallery/Assets/ModelIcons/MiniMax.dark.svg new file mode 100644 index 00000000..b69e9758 --- /dev/null +++ b/AIDevGallery/Assets/ModelIcons/MiniMax.dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/AIDevGallery/Assets/ModelIcons/MiniMax.light.png b/AIDevGallery/Assets/ModelIcons/MiniMax.light.png new file mode 100644 index 00000000..4f139083 Binary files /dev/null and b/AIDevGallery/Assets/ModelIcons/MiniMax.light.png differ diff --git a/AIDevGallery/Assets/ModelIcons/MiniMax.light.svg b/AIDevGallery/Assets/ModelIcons/MiniMax.light.svg new file mode 100644 index 00000000..694ac3af --- /dev/null +++ b/AIDevGallery/Assets/ModelIcons/MiniMax.light.svg @@ -0,0 +1,3 @@ + + + diff --git a/AIDevGallery/Assets/ModelIcons/MiniMax.svg b/AIDevGallery/Assets/ModelIcons/MiniMax.svg new file mode 100644 index 00000000..c4b94877 --- /dev/null +++ b/AIDevGallery/Assets/ModelIcons/MiniMax.svg @@ -0,0 +1,3 @@ + + + diff --git a/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml new file mode 100644 index 00000000..0de92f53 --- /dev/null +++ b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml.cs b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml.cs new file mode 100644 index 00000000..84b83d32 --- /dev/null +++ b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/MiniMaxPickerView.xaml.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.ExternalModelUtils; +using AIDevGallery.Models; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; + +namespace AIDevGallery.Controls.ModelPickerViews; + +internal sealed partial class MiniMaxPickerView : BaseModelPickerView +{ + private ObservableCollection models = new ObservableCollection(); + + public MiniMaxPickerView() + { + this.InitializeComponent(); + } + + public override Task Load(List types) + { + return Load(); + } + + private async Task Load() + { + VisualStateManager.GoToState(this, "ShowLoading", true); + + var miniMaxModels = await MiniMaxModelProvider.Instance.GetModelsAsync(); + + if (miniMaxModels == null || !miniMaxModels.Any()) + { + VisualStateManager.GoToState(this, "ShowInput", true); + } + else + { + miniMaxModels.ToList().ForEach(models.Add); + VisualStateManager.GoToState(this, "ShowModels", true); + } + } + + private void CopyUrl_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem btn && btn.Tag is ModelDetails details) + { + var url = ExternalModelHelper.GetModelUrl(details); + if (string.IsNullOrEmpty(url)) + { + return; + } + + var dataPackage = new DataPackage(); + dataPackage.SetText(url); + Clipboard.SetContentWithOptions(dataPackage, null); + } + } + + private void ViewModelDetails_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem btn && btn.Tag is ModelDetails details) + { + var modelDetailsUrl = ExternalModelHelper.GetModelDetailsUrl(details); + if (string.IsNullOrEmpty(modelDetailsUrl)) + { + return; + } + + Process.Start(new ProcessStartInfo + { + FileName = modelDetailsUrl, + UseShellExecute = true + }); + } + } + + private void ModelSelectionItemsView_SelectionChanged(ItemsView sender, ItemsViewSelectionChangedEventArgs args) + { + if (sender.SelectedItem is ModelDetails details) + { + OnSelectedModelChanged(this, details); + } + } + + public override void SelectModel(ModelDetails? modelDetails) + { + if (modelDetails != null && models.Contains(modelDetails)) + { + var foundModel = models.FirstOrDefault(m => m.Id == modelDetails.Id); + if (foundModel != null) + { + DispatcherQueue.TryEnqueue(() => ModelSelectionItemsView.Select(models.IndexOf(foundModel))); + } + else + { + DispatcherQueue.TryEnqueue(() => ModelSelectionItemsView.DeselectAll()); + } + } + else + { + DispatcherQueue.TryEnqueue(() => ModelSelectionItemsView.DeselectAll()); + } + } + + private void SaveKeyButton_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrEmpty(MiniMaxKeyTextBox.Text)) + { + return; + } + + MiniMaxModelProvider.MiniMaxKey = MiniMaxKeyTextBox.Text; + _ = Load(); + } + + private void RemoveKeyButton_Click(object sender, RoutedEventArgs e) + { + MiniMaxModelProvider.MiniMaxKey = null; + MiniMaxModelProvider.Instance.ClearCachedModels(); + _ = Load(); + } +} diff --git a/AIDevGallery/Controls/ModelPicker/ModelPickerViews/ModelPickerDefinitions.cs b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/ModelPickerDefinitions.cs index ff2d9fab..61bcd656 100644 --- a/AIDevGallery/Controls/ModelPicker/ModelPickerViews/ModelPickerDefinitions.cs +++ b/AIDevGallery/Controls/ModelPicker/ModelPickerViews/ModelPickerDefinitions.cs @@ -59,6 +59,15 @@ internal class ModelPickerDefinition CreatePicker = () => new OpenAIPickerView() } }, + { + "minimax", new ModelPickerDefinition() + { + Name = "MiniMax", + Id = "minimax", + Icon = $"ms-appx:///Assets/ModelIcons/MiniMax{AppUtils.GetThemeAssetSuffix()}.png", + CreatePicker = () => new MiniMaxPickerView() + } + }, { "lemonade", new ModelPickerDefinition() { diff --git a/AIDevGallery/ExternalModelUtils/ExternalModelHelper.cs b/AIDevGallery/ExternalModelUtils/ExternalModelHelper.cs index 13512353..bbe1996a 100644 --- a/AIDevGallery/ExternalModelUtils/ExternalModelHelper.cs +++ b/AIDevGallery/ExternalModelUtils/ExternalModelHelper.cs @@ -21,6 +21,7 @@ internal static class ExternalModelHelper FoundryLocalModelProvider.Instance, OllamaModelProvider.Instance, OpenAIModelProvider.Instance, + MiniMaxModelProvider.Instance, LemonadeModelProvider.Instance ]; diff --git a/AIDevGallery/ExternalModelUtils/MiniMaxModelProvider.cs b/AIDevGallery/ExternalModelUtils/MiniMaxModelProvider.cs new file mode 100644 index 00000000..78ca7072 --- /dev/null +++ b/AIDevGallery/ExternalModelUtils/MiniMaxModelProvider.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AIDevGallery.Models; +using AIDevGallery.Utils; +using Microsoft.Extensions.AI; +using OpenAI; +using System; +using System.ClientModel; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AIDevGallery.ExternalModelUtils; + +internal class MiniMaxModelProvider : IExternalModelProvider +{ + public static MiniMaxModelProvider Instance { get; } = new MiniMaxModelProvider(); + + private const string KeyName = "AI_DEV_GALLERY_MINIMAX_API_KEY"; + + public static string? MiniMaxKey + { + get + { + return CredentialManager.ReadCredential(KeyName); + } + set + { + if (value != null) + { + CredentialManager.WriteCredential(KeyName, value); + } + else + { + CredentialManager.DeleteCredential(KeyName); + } + } + } + + public string Name => "MiniMax"; + + public HardwareAccelerator ModelHardwareAccelerator => HardwareAccelerator.MINIMAX; + + public List NugetPackageReferences => ["Microsoft.Extensions.AI.OpenAI"]; + + public string ProviderDescription => "The model will run on the cloud via MiniMax"; + + public string UrlPrefix => "minimax://"; + + public string Icon => $"MiniMax{AppUtils.GetThemeAssetSuffix()}.svg"; + + public string Url => "https://api.minimax.io/v1"; + + public string? IChatClientImplementationNamespace { get; } = "OpenAI"; + + // MiniMax models with their display names + private static readonly (string Id, string DisplayName, string Description)[] KnownModels = + [ + ("MiniMax-M2.7", "MiniMax-M2.7", "MiniMax M2.7 - latest flagship model with 1M context"), + ("MiniMax-M2.5", "MiniMax-M2.5", "MiniMax M2.5 with 204K context window"), + ("MiniMax-M2.5-highspeed", "MiniMax-M2.5-highspeed", "MiniMax M2.5 high-speed variant with 204K context"), + ]; + + public string? GetDetailsUrl(ModelDetails details) + { + return "https://platform.minimaxi.com/document/Models"; + } + + public IChatClient? GetIChatClient(string url) + { + var modelId = url.Replace(UrlPrefix, string.Empty); + if (string.IsNullOrEmpty(modelId) || string.IsNullOrEmpty(MiniMaxKey)) + { + return null; + } + + return new OpenAIClient(new ApiKeyCredential(MiniMaxKey), new OpenAIClientOptions + { + Endpoint = new Uri(Url) + }).GetChatClient(modelId).AsIChatClient(); + } + + public string? GetIChatClientString(string url) + { + var modelId = url.Replace(UrlPrefix, string.Empty); + + return $"new OpenAIClient(new ApiKeyCredential(\"MINIMAX_API_KEY\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{Url}\") }}).GetChatClient(\"{modelId}\").AsIChatClient()"; + } + + public void ClearCachedModels() + { + // Static model list, nothing to clear + } + + public Task> GetModelsAsync(bool ignoreCached = false, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(MiniMaxKey)) + { + return Task.FromResult>([]); + } + + var models = KnownModels.Select(m => new ModelDetails + { + Id = $"minimax-{m.Id}", + Name = m.DisplayName, + Url = $"{UrlPrefix}{m.Id}", + Description = m.Description, + HardwareAccelerators = [HardwareAccelerator.MINIMAX], + Size = 0, + SupportedOnQualcomm = true, + ParameterSize = string.Empty, + }); + + return Task.FromResult(models); + } +} diff --git a/AIDevGallery/Helpers/ModelDetailsHelper.cs b/AIDevGallery/Helpers/ModelDetailsHelper.cs index 8f9bed32..55f7d7be 100644 --- a/AIDevGallery/Helpers/ModelDetailsHelper.cs +++ b/AIDevGallery/Helpers/ModelDetailsHelper.cs @@ -204,6 +204,7 @@ public static bool IsLanguageModel(this ModelDetails modelDetails) { return modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.OLLAMA) || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.OPENAI) || + modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.MINIMAX) || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.FOUNDRYLOCAL) || modelDetails.HardwareAccelerators.Contains(HardwareAccelerator.LEMONADE) || modelDetails.Url.StartsWith("useradded-languagemodel", System.StringComparison.InvariantCultureIgnoreCase) || diff --git a/AIDevGallery/Models/Samples.cs b/AIDevGallery/Models/Samples.cs index 3163d476..fbde6d1f 100644 --- a/AIDevGallery/Models/Samples.cs +++ b/AIDevGallery/Models/Samples.cs @@ -176,6 +176,7 @@ internal enum HardwareAccelerator WCRAPI, OLLAMA, OPENAI, + MINIMAX, FOUNDRYLOCAL, LEMONADE, NPU, diff --git a/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs b/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs index bdd9c1f7..9cc3895a 100644 --- a/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs +++ b/AIDevGallery/ProjectGenerator/Template/Utils/HardwareAccelerator.cs @@ -9,6 +9,7 @@ internal enum HardwareAccelerator WCRAPI, OLLAMA, OPENAI, + MINIMAX, NPU, GPU, VitisAI, diff --git a/docs/AddingSamples.md b/docs/AddingSamples.md index f620db91..598e8ee5 100644 --- a/docs/AddingSamples.md +++ b/docs/AddingSamples.md @@ -201,7 +201,7 @@ This format groups multiple related model families together with additional meta - `ReadmeUrl`: Direct link to the model family's README markdown that the app fetches and renders in-app - `Models`: Dictionary of model variations with different hardware requirements - `Url`: HuggingFace URL - can point to repo root, a subfolder, or a single file - - `HardwareAccelerator`: One or more of: "CPU", "GPU", "DML", "QNN", "NPU", "WCRAPI", "OLLAMA", "OPENAI", "VitisAI", "OpenVINO" (or an array like ["CPU", "GPU"]) + - `HardwareAccelerator`: One or more of: "CPU", "GPU", "DML", "QNN", "NPU", "WCRAPI", "OLLAMA", "OPENAI", "MINIMAX", "VitisAI", "OpenVINO" (or an array like ["CPU", "GPU"]) - `Size`: Model size in bytes - `License`: License type (e.g., "mit", "apache-2.0") - `PromptTemplate`: Prompt template to use (for language models)