diff --git a/CHANGELOG.md b/CHANGELOG.md index c739ad8a..2fc8c2ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.5.1 +### Added +- `--skip-install` default launch argument for Automatic1111 Package +### Fixed +- Fixed Prompt weights showing syntax error in locales where decimal separator is not a period + ## v2.5.0 ### Added - Added Inference, a built-in native Stable Diffusion interface, powered by ComfyUI @@ -12,6 +18,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Added the ability to Favorite models in the Model Browser - Added "Favorites" sort option to the Model Browser - Added notification flyout for new available updates. Dismiss to hide until the next update version. +- Added Italian UI language options, thanks to Marco Capelli for the translations ### Changed - Model Browser page size is now 20 instead of 14 - Update changelog now only shows the difference between the current version and the latest version diff --git a/StabilityMatrix.Avalonia/Models/Inference/Prompt.cs b/StabilityMatrix.Avalonia/Models/Inference/Prompt.cs index a7a7ba73..c4c76fa4 100644 --- a/StabilityMatrix.Avalonia/Models/Inference/Prompt.cs +++ b/StabilityMatrix.Avalonia/Models/Inference/Prompt.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -292,7 +293,9 @@ public record Prompt ]; // Convert to double - if (!double.TryParse(modelWeight, out var weightValue)) + if ( + !double.TryParse(modelWeight, CultureInfo.InvariantCulture, out var weightValue) + ) { throw PromptValidationError.Network_InvalidWeight( currentToken.StartIndex, diff --git a/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs b/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs index 3a958878..74b22d44 100644 --- a/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs +++ b/StabilityMatrix.Avalonia/Models/TagCompletion/TokenizerProvider.cs @@ -8,16 +8,15 @@ namespace StabilityMatrix.Avalonia.Models.TagCompletion; public class TokenizerProvider : ITokenizerProvider { private readonly Registry registry = new(new RegistryOptions(ThemeName.DarkPlus)); - private IGrammar grammar; - - public TokenizerProvider() - { - SetPromptGrammar(); - } - + private IGrammar? grammar; + /// public ITokenizeLineResult TokenizeLine(string lineText) { + if (grammar is null) + { + SetPromptGrammar(); + } return grammar.TokenizeLine(lineText); } @@ -27,7 +26,7 @@ public class TokenizerProvider : ITokenizerProvider using var stream = Assets.ImagePromptLanguageJson.Open(); grammar = registry.LoadGrammarFromStream(stream); } - + public void SetGrammar(string scopeName) { grammar = registry.LoadGrammar(scopeName); diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index e07e8f30..4b71dec1 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -72,7 +72,6 @@ - diff --git a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml index 250f5a30..e77a3cfa 100644 --- a/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml +++ b/StabilityMatrix.Avalonia/Views/LaunchPageView.axaml @@ -121,6 +121,7 @@ Margin="8,8,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" + IsEnabled="{Binding RunningPackage, Converter={x:Static ObjectConverters.IsNull}}" ItemsSource="{Binding InstalledPackages}" SelectedItem="{Binding SelectedPackage}"> diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index 5d0a275d..270d8cc8 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -144,6 +144,13 @@ public class A3WebUI : BaseGitPackage InitialValue = false, Options = new() { "--no-download-sd-model" } }, + new() + { + Name = "Skip Install", + Type = LaunchOptionType.Bool, + InitialValue = true, + Options = new() { "--skip-install" } + }, LaunchOptionDefinition.Extras }; diff --git a/StabilityMatrix.Tests/Avalonia/LoadableViewModelBaseTests.cs b/StabilityMatrix.Tests/Avalonia/LoadableViewModelBaseTests.cs index 7691bc87..c7c8ddac 100644 --- a/StabilityMatrix.Tests/Avalonia/LoadableViewModelBaseTests.cs +++ b/StabilityMatrix.Tests/Avalonia/LoadableViewModelBaseTests.cs @@ -2,9 +2,8 @@ using System.Text.Json.Serialization; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Moq; +using NSubstitute; using StabilityMatrix.Avalonia.Models; -using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; #pragma warning disable CS0657 // Not a valid attribute location for this declaration @@ -16,9 +15,9 @@ public class TestLoadableViewModel : LoadableViewModelBase { [JsonInclude] public string? Included { get; set; } - + public int Id { get; set; } - + [JsonIgnore] public int Ignored { get; set; } } @@ -43,10 +42,10 @@ public partial class TestLoadableViewModelObservable : LoadableViewModelBase [ObservableProperty] [property: JsonIgnore] private string? title; - + [ObservableProperty] private int id; - + [RelayCommand] private void TestCommand() { @@ -76,9 +75,9 @@ public class LoadableViewModelBaseTests Id = 123, Ignored = 456, }; - + var state = vm.SaveStateToJsonObject(); - + // [JsonInclude] and not marked property should be serialized. // Ignored property should be ignored. Assert.AreEqual(2, state.Count); @@ -90,17 +89,13 @@ public class LoadableViewModelBaseTests public void TestSaveStateToJsonObject_Observable() { // Mvvm ObservableProperty should be serialized. - var vm = new TestLoadableViewModelObservable - { - Title = "abc", - Id = 123, - }; + var vm = new TestLoadableViewModelObservable { Title = "abc", Id = 123, }; var state = vm.SaveStateToJsonObject(); - + // Title should be ignored since it has [JsonIgnore] // Command should be ignored from excluded type rules // Id should be serialized - + Assert.AreEqual(1, state.Count); Assert.AreEqual(123, state["Id"].Deserialize()); } @@ -110,23 +105,20 @@ public class LoadableViewModelBaseTests { // Properties of type IJsonLoadableState should be serialized by calling their // SaveStateToJsonObject method. - + // Make a mock IJsonLoadableState - var mockState = new Mock(); + var mockState = Substitute.For(); + + var vm = new TestLoadableViewModelNestedInterface { NestedState = mockState }; - var vm = new TestLoadableViewModelNestedInterface - { - NestedState = mockState.Object - }; - // Serialize var state = vm.SaveStateToJsonObject(); - + // Check results Assert.AreEqual(1, state.Count); - + // Check that SaveStateToJsonObject was called - mockState.Verify(x => x.SaveStateToJsonObject(), Times.Once); + mockState.Received().SaveStateToJsonObject(); } [TestMethod] @@ -139,20 +131,20 @@ public class LoadableViewModelBaseTests Id = 123, Ignored = 456, }; - + var state = vm.SaveStateToJsonObject(); - + // Create a new instance and load the state var vm2 = new TestLoadableViewModel(); vm2.LoadStateFromJsonObject(state); - + // Check [JsonInclude] and not marked property was loaded Assert.AreEqual("abc", vm2.Included); Assert.AreEqual(123, vm2.Id); // Check ignored property was not loaded Assert.AreEqual(0, vm2.Ignored); } - + [TestMethod] public void TestLoadStateFromJsonObject_Nested_DefaultCtor() { @@ -163,27 +155,24 @@ public class LoadableViewModelBaseTests Id = 123, Ignored = 456, }; - - var vm = new TestLoadableViewModelNested - { - NestedState = nested - }; - + + var vm = new TestLoadableViewModelNested { NestedState = nested }; + var state = vm.SaveStateToJsonObject(); - + // Create a new instance with null NestedState, rely on default ctor var vm2 = new TestLoadableViewModelNested(); vm2.LoadStateFromJsonObject(state); - + // Check nested state was loaded Assert.IsNotNull(vm2.NestedState); - - var loadedNested = (TestLoadableViewModel) vm2.NestedState; + + var loadedNested = (TestLoadableViewModel)vm2.NestedState; Assert.AreEqual("abc", loadedNested.Included); Assert.AreEqual(123, loadedNested.Id); Assert.AreEqual(0, loadedNested.Ignored); } - + [TestMethod] public void TestLoadStateFromJsonObject_Nested_Existing() { @@ -194,63 +183,60 @@ public class LoadableViewModelBaseTests Id = 123, Ignored = 456, }; - - var vm = new TestLoadableViewModelNestedInterface - { - NestedState = nested - }; - + + var vm = new TestLoadableViewModelNestedInterface { NestedState = nested }; + var state = vm.SaveStateToJsonObject(); - + // Create a new instance with existing NestedState var vm2 = new TestLoadableViewModelNestedInterface { NestedState = new TestLoadableViewModel() }; vm2.LoadStateFromJsonObject(state); - + // Check nested state was loaded Assert.IsNotNull(vm2.NestedState); - - var loadedNested = (TestLoadableViewModel) vm2.NestedState; + + var loadedNested = (TestLoadableViewModel)vm2.NestedState; Assert.AreEqual("abc", loadedNested.Included); Assert.AreEqual(123, loadedNested.Id); Assert.AreEqual(0, loadedNested.Ignored); } - + [TestMethod] public void TestLoadStateFromJsonObject_ReadOnly() { var vm = new TestLoadableViewModelReadOnly(456); - + var state = vm.SaveStateToJsonObject(); - + // Check no properties were serialized Assert.AreEqual(0, state.Count); - + // Create a new instance and load the state var vm2 = new TestLoadableViewModelReadOnly(123); vm2.LoadStateFromJsonObject(state); - + // Read only property should have been ignored Assert.AreEqual(123, vm2.ReadOnly); } - + [TestMethod] public void TestLoadStateFromJsonObject_ReadOnlyLoadable() { var vm = new TestLoadableViewModelReadOnlyLoadable { - ReadOnlyLoadable = - { - Included = "abc-123" - } + ReadOnlyLoadable = { Included = "abc-123" } }; var state = vm.SaveStateToJsonObject(); - + // Check readonly loadable property was serialized Assert.AreEqual(1, state.Count); - Assert.AreEqual("abc-123", state["ReadOnlyLoadable"].Deserialize()!.Included); + Assert.AreEqual( + "abc-123", + state["ReadOnlyLoadable"].Deserialize()!.Included + ); } } diff --git a/StabilityMatrix.Tests/Avalonia/PromptTests.cs b/StabilityMatrix.Tests/Avalonia/PromptTests.cs new file mode 100644 index 00000000..60b556a0 --- /dev/null +++ b/StabilityMatrix.Tests/Avalonia/PromptTests.cs @@ -0,0 +1,114 @@ +using System.Globalization; +using System.Reflection; +using NSubstitute; +using StabilityMatrix.Avalonia.Extensions; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.Models.TagCompletion; +using StabilityMatrix.Core.Models.Tokens; +using TextMateSharp.Grammars; +using TextMateSharp.Registry; + +namespace StabilityMatrix.Tests.Avalonia; + +[TestClass] +public class PromptTests +{ + private ITokenizerProvider tokenizerProvider = null!; + + [TestInitialize] + public void TestInitialize() + { + tokenizerProvider = Substitute.For(); + + var promptSyntaxFile = Assembly + .GetExecutingAssembly() + .GetManifestResourceStream("StabilityMatrix.Tests.ImagePrompt.tmLanguage.json")!; + + var registry = new Registry(new RegistryOptions(ThemeName.DarkPlus)); + var grammar = registry.LoadGrammarFromStream(promptSyntaxFile); + + tokenizerProvider + .TokenizeLine(Arg.Any()) + .Returns(x => grammar.TokenizeLine(x.Arg())); + } + + [TestMethod] + public void TestPromptProcessedText() + { + var prompt = Prompt.FromRawText("test", tokenizerProvider); + + prompt.Process(); + + Assert.AreEqual("test", prompt.ProcessedText); + } + + [TestMethod] + public void TestPromptWeightParsing() + { + var prompt = Prompt.FromRawText("", tokenizerProvider); + + prompt.Process(); + + // Output should have no loras + Assert.AreEqual("", prompt.ProcessedText); + + var network = prompt.ExtraNetworks[0]; + + Assert.AreEqual(PromptExtraNetworkType.Lora, network.Type); + Assert.AreEqual("my_model", network.Name); + Assert.AreEqual(1.5f, network.ModelWeight); + } + + /// + /// Tests that we can parse decimal numbers with different cultures + /// + [TestMethod] + public void TestPromptWeightParsing_DecimalSeparatorCultures_ShouldParse() + { + var prompt = Prompt.FromRawText("", tokenizerProvider); + + // Cultures like de-DE use commas as decimal separators, check that we can parse those too + ExecuteWithCulture(prompt.Process, CultureInfo.GetCultureInfo("de-DE")); + + // Output should have no loras + Assert.AreEqual("", prompt.ProcessedText); + + var network = prompt.ExtraNetworks![0]; + + Assert.AreEqual(PromptExtraNetworkType.Lora, network.Type); + Assert.AreEqual("my_model", network.Name); + Assert.AreEqual(1.5f, network.ModelWeight); + } + + private static T? ExecuteWithCulture(Func func, CultureInfo culture) + { + var result = default(T); + + var thread = new Thread(() => + { + result = func(); + }) + { + CurrentCulture = culture + }; + + thread.Start(); + thread.Join(); + + return result; + } + + private static void ExecuteWithCulture(Action func, CultureInfo culture) + { + var thread = new Thread(() => + { + func(); + }) + { + CurrentCulture = culture + }; + + thread.Start(); + thread.Join(); + } +} diff --git a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj index 7bd9f4d0..e82a92a8 100644 --- a/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj +++ b/StabilityMatrix.Tests/StabilityMatrix.Tests.csproj @@ -15,13 +15,13 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -32,7 +32,10 @@ - + + + +