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 @@
-
+
+
+
+