Browse Source

Merge branch 'dev' of https://github.com/ionite34/StabilityMatrix into consolidate

pull/240/head
JT 1 year ago
parent
commit
05e0da3bee
  1. 4
      CHANGELOG.md
  2. 2
      README.md
  3. 28
      StabilityMatrix.Avalonia/Converters/CultureInfoDisplayConverter.cs
  4. 3
      StabilityMatrix.Avalonia/DesignData/MockImageIndexService.cs
  5. 95
      StabilityMatrix.Avalonia/Helpers/ImageSearcher.cs
  6. 3
      StabilityMatrix.Avalonia/Languages/Cultures.cs
  7. 681
      StabilityMatrix.Avalonia/Languages/Resources.ru-ru.resx
  8. 11
      StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs
  9. 48
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  10. 9
      StabilityMatrix.Avalonia/Views/SettingsPage.axaml
  11. 35
      StabilityMatrix.Core/Extensions/DynamicDataExtensions.cs
  12. 40
      StabilityMatrix.Core/Models/Database/LocalImageFile.cs
  13. 3
      StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs
  14. 80
      StabilityMatrix.Core/Models/GenerationParameters.cs
  15. 10
      StabilityMatrix.Core/Services/ImageIndexService.cs
  16. 70
      StabilityMatrix.Tests/Models/GenerationParametersTests.cs

4
CHANGELOG.md

@ -34,6 +34,10 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Fixed Inference Prompt Completion window sometimes not showing while typing
- Fixed "Show Model Images" toggle on Checkpoints page sometimes displaying cut-off model images
## v2.5.6
### Added
- Added Russian UI language option, thanks to aolko for the translation
## v2.5.5
### Added
- Added Spanish UI language options, thanks to Carlos Baena and Lautaroturina for the translations

2
README.md

@ -102,6 +102,8 @@ Stability Matrix is now available in the following languages, thanks to our comm
- 🇪🇸 Español
- Carlos Baena
- Lautaroturina
- 🇷🇺 Русский
- aolko
If you would like to contribute a translation, please create an issue or contact us on Discord. Include an email where we'll send an invite to our [POEditor](https://poeditor.com/) project.

28
StabilityMatrix.Avalonia/Converters/CultureInfoDisplayConverter.cs

@ -0,0 +1,28 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace StabilityMatrix.Avalonia.Converters;
public class CultureInfoDisplayConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not CultureInfo cultureInfo)
return null;
return cultureInfo.TextInfo.ToTitleCase(cultureInfo.NativeName);
}
/// <inheritdoc />
public object ConvertBack(
object? value,
Type targetType,
object? parameter,
CultureInfo culture
)
{
throw new NotImplementedException();
}
}

3
StabilityMatrix.Avalonia/DesignData/MockImageIndexService.cs

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using DynamicData;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Services;
@ -51,7 +52,7 @@ public class MockImageIndexService : IImageIndexService
)
};
indexCollection.ItemsSource.EditDiff(toAdd, LocalImageFile.Comparer);
indexCollection.ItemsSource.EditDiff(toAdd);
return Task.CompletedTask;
}

95
StabilityMatrix.Avalonia/Helpers/ImageSearcher.cs

@ -0,0 +1,95 @@
using System;
using FuzzySharp;
using FuzzySharp.PreProcess;
using StabilityMatrix.Core.Models.Database;
namespace StabilityMatrix.Avalonia.Helpers;
public class ImageSearcher
{
public int MinimumFuzzScore { get; init; } = 80;
public ImageSearchOptions SearchOptions { get; init; } = ImageSearchOptions.All;
public Func<LocalImageFile, bool> GetPredicate(string? searchQuery)
{
if (string.IsNullOrEmpty(searchQuery))
{
return _ => true;
}
return file =>
{
if (file.FileName.Contains(searchQuery, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (
SearchOptions.HasFlag(ImageSearchOptions.FileName)
&& Fuzz.WeightedRatio(searchQuery, file.FileName, PreprocessMode.Full)
> MinimumFuzzScore
)
{
return true;
}
// Generation params
if (file.GenerationParameters is { } parameters)
{
if (
SearchOptions.HasFlag(ImageSearchOptions.PositivePrompt)
&& (
parameters.PositivePrompt?.Contains(
searchQuery,
StringComparison.OrdinalIgnoreCase
) ?? false
)
|| SearchOptions.HasFlag(ImageSearchOptions.NegativePrompt)
&& (
parameters.NegativePrompt?.Contains(
searchQuery,
StringComparison.OrdinalIgnoreCase
) ?? false
)
|| SearchOptions.HasFlag(ImageSearchOptions.Seed)
&& parameters.Seed
.ToString()
.StartsWith(searchQuery, StringComparison.OrdinalIgnoreCase)
|| SearchOptions.HasFlag(ImageSearchOptions.Sampler)
&& (
parameters.Sampler?.StartsWith(
searchQuery,
StringComparison.OrdinalIgnoreCase
) ?? false
)
|| SearchOptions.HasFlag(ImageSearchOptions.ModelName)
&& (
parameters.ModelName?.StartsWith(
searchQuery,
StringComparison.OrdinalIgnoreCase
) ?? false
)
)
{
return true;
}
}
return false;
};
}
[Flags]
public enum ImageSearchOptions
{
None = 0,
FileName = 1 << 0,
PositivePrompt = 1 << 1,
NegativePrompt = 1 << 2,
Seed = 1 << 3,
Sampler = 1 << 4,
ModelName = 1 << 5,
All = int.MaxValue
}
}

3
StabilityMatrix.Avalonia/Languages/Cultures.cs

@ -24,7 +24,8 @@ public static class Cultures
["zh-Hant"] = new("zh-Hant"),
["it-IT"] = new("it-IT"),
["fr-FR"] = new("fr-FR"),
["es"] = new("es")
["es"] = new("es"),
["ru-RU"] = new("ru-RU")
};
public static IReadOnlyList<CultureInfo> SupportedCultures =>

681
StabilityMatrix.Avalonia/Languages/Resources.ru-ru.resx

@ -0,0 +1,681 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Action_Launch" xml:space="preserve">
<value>Запустить</value>
</data>
<data name="Action_Quit" xml:space="preserve">
<value>Выйти</value>
</data>
<data name="Action_Save" xml:space="preserve">
<value>Сохранить</value>
</data>
<data name="Action_Cancel" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="Label_Language" xml:space="preserve">
<value>Язык</value>
</data>
<data name="Text_RelaunchRequiredToApplyLanguage" xml:space="preserve">
<value>Чтобы применить новый язык требуется перезапуск</value>
</data>
<data name="Action_Relaunch" xml:space="preserve">
<value>Перезапустить</value>
</data>
<data name="Action_RelaunchLater" xml:space="preserve">
<value>Перезапустить позже</value>
</data>
<data name="Label_RelaunchRequired" xml:space="preserve">
<value>Требуется перезапуск</value>
</data>
<data name="Label_UnknownPackage" xml:space="preserve">
<value>Неизвестный пакет</value>
</data>
<data name="Action_Import" xml:space="preserve">
<value>Импорт</value>
</data>
<data name="Label_PackageType" xml:space="preserve">
<value>Тип пакета</value>
</data>
<data name="Label_Version" xml:space="preserve">
<value>Версия</value>
</data>
<data name="Label_VersionType" xml:space="preserve">
<value>Тип версии</value>
</data>
<data name="Label_Releases" xml:space="preserve">
<value>Релизы</value>
</data>
<data name="Label_Branches" xml:space="preserve">
<value>Ветки</value>
</data>
<data name="Label_DragAndDropCheckpointsHereToImport" xml:space="preserve">
<value>Перетащите модели сюда для импорта</value>
</data>
<data name="Label_Emphasis" xml:space="preserve">
<value>Усиление акцента</value>
</data>
<data name="Label_Deemphasis" xml:space="preserve">
<value>Ослабление акцента</value>
</data>
<data name="Label_EmbeddingsOrTextualInversion" xml:space="preserve">
<value>Вложения / Текстовые Инверсии</value>
</data>
<data name="Label_NetworksLoraOrLycoris" xml:space="preserve">
<value>Сети (Лора / ЛиКОРИС)</value>
</data>
<data name="Label_Comments" xml:space="preserve">
<value>Комментарии</value>
</data>
<data name="Label_ShowPixelGridAtHighZoomLevels" xml:space="preserve">
<value>Показывать пискельную сетку при максимальном масштабе</value>
</data>
<data name="Label_Steps" xml:space="preserve">
<value>Шаги</value>
</data>
<data name="Label_StepsBase" xml:space="preserve">
<value>Шаги - Базовые</value>
</data>
<data name="Label_StepsRefiner" xml:space="preserve">
<value>Шаги - Обработчик</value>
</data>
<data name="Label_CFGScale" xml:space="preserve">
<value>Внимание к запросу</value>
</data>
<data name="Label_DenoisingStrength" xml:space="preserve">
<value>Сила шумоподавления</value>
</data>
<data name="Label_Width" xml:space="preserve">
<value>Ширина</value>
</data>
<data name="Label_Height" xml:space="preserve">
<value>Высота</value>
</data>
<data name="Label_Refiner" xml:space="preserve">
<value>Обработчик</value>
</data>
<data name="Label_VAE" xml:space="preserve">
<value>VAE</value>
</data>
<data name="Label_Model" xml:space="preserve">
<value>Модель</value>
</data>
<data name="Action_Connect" xml:space="preserve">
<value>Соединиться</value>
</data>
<data name="Label_ConnectingEllipsis" xml:space="preserve">
<value>Соединяюсь...</value>
</data>
<data name="Action_Close" xml:space="preserve">
<value>Закрыть</value>
</data>
<data name="Label_WaitingToConnectEllipsis" xml:space="preserve">
<value>Жду соединения...</value>
</data>
<data name="Label_UpdateAvailable" xml:space="preserve">
<value>Доступно обновление</value>
</data>
<data name="Label_BecomeAPatron" xml:space="preserve">
<value>Поддержать на Патреоне</value>
</data>
<data name="Label_JoinDiscord" xml:space="preserve">
<value>Дискорд сервер проекта</value>
</data>
<data name="Label_Downloads" xml:space="preserve">
<value>Загрузки</value>
</data>
<data name="Action_Install" xml:space="preserve">
<value>Установить</value>
</data>
<data name="Label_SkipSetup" xml:space="preserve">
<value>Пропустить установку в первый раз</value>
</data>
<data name="Label_UnexpectedErrorOccurred" xml:space="preserve">
<value>Произошла неизвестная ошибка</value>
</data>
<data name="Action_ExitApplication" xml:space="preserve">
<value>Выйти из программы</value>
</data>
<data name="Label_DisplayName" xml:space="preserve">
<value>Название</value>
</data>
<data name="Label_InstallationWithThisNameExists" xml:space="preserve">
<value>Установка с таким названием уже существует.</value>
</data>
<data name="Label_PleaseChooseDifferentName" xml:space="preserve">
<value>Пожалуйста выберите другое название или другой путь установки.</value>
</data>
<data name="Label_AdvancedOptions" xml:space="preserve">
<value>Продвинутые настройки</value>
</data>
<data name="Label_Commit" xml:space="preserve">
<value>Коммит</value>
</data>
<data name="Label_SharedModelFolderStrategy" xml:space="preserve">
<value>Способ расшаривания моделей</value>
</data>
<data name="Label_PyTorchVersion" xml:space="preserve">
<value>Версия PyTorch</value>
</data>
<data name="Label_CloseDialogWhenFinished" xml:space="preserve">
<value>Закрыть диалог по завершении</value>
</data>
<data name="Label_DataDirectory" xml:space="preserve">
<value>Папка с данными</value>
</data>
<data name="Label_DataDirectoryExplanation" xml:space="preserve">
<value>Папка в которую будут установлены модели, Лоры, интерфейсы, настройки, и прочее.</value>
</data>
<data name="Label_FatWarning" xml:space="preserve">
<value>Вы можете столкнуться с ошибками при использовании диска FAT32 или exFAT. Выберите другой диск для более плавной работы.</value>
</data>
<data name="Label_PortableMode" xml:space="preserve">
<value>Портативный режим</value>
</data>
<data name="Label_PortableModeExplanation" xml:space="preserve">
<value>В портативном режиме все данные и настройки будут храниться в той же папке, что и приложение. Вы сможете переместить приложение вместе с его папкой с данными (&quot;Data&quot;) в другое место или на другой компьютер.</value>
</data>
<data name="Action_Continue" xml:space="preserve">
<value>Продолжить</value>
</data>
<data name="Label_PreviousImage" xml:space="preserve">
<value>Пред. картинка</value>
</data>
<data name="Label_NextImage" xml:space="preserve">
<value>След. картинка</value>
</data>
<data name="Label_ModelDescription" xml:space="preserve">
<value>Описание модели</value>
</data>
<data name="Label_NewVersionAvailable" xml:space="preserve">
<value>Доступна новая версия Stability Matrix!</value>
</data>
<data name="Label_ImportLatest" xml:space="preserve">
<value>Импорт последней -</value>
</data>
<data name="Label_AllVersions" xml:space="preserve">
<value>Все версии</value>
</data>
<data name="Label_ModelSearchWatermark" xml:space="preserve">
<value>Поиск моделей, #тегов или @авторов</value>
</data>
<data name="Action_Search" xml:space="preserve">
<value>Поиск</value>
</data>
<data name="Label_Sort" xml:space="preserve">
<value>Сортировка</value>
</data>
<data name="Label_TimePeriod" xml:space="preserve">
<value>Промежуток</value>
</data>
<data name="Label_ModelType" xml:space="preserve">
<value>Тип модели</value>
</data>
<data name="Label_BaseModel" xml:space="preserve">
<value>Базовая модель</value>
</data>
<data name="Label_ShowNsfwContent" xml:space="preserve">
<value>Показывать 18+ контент</value>
</data>
<data name="Label_DataProvidedByCivitAi" xml:space="preserve">
<value>Данные предоставлены CivitAI</value>
</data>
<data name="Label_Page" xml:space="preserve">
<value>Страница</value>
</data>
<data name="Label_FirstPage" xml:space="preserve">
<value>Первая страница</value>
</data>
<data name="Label_PreviousPage" xml:space="preserve">
<value>Пред. страницаыдущая</value>
</data>
<data name="Label_NextPage" xml:space="preserve">
<value>Следущая страница</value>
</data>
<data name="Label_LastPage" xml:space="preserve">
<value>Последняя страница</value>
</data>
<data name="Action_Rename" xml:space="preserve">
<value>Переименовать</value>
</data>
<data name="Action_Delete" xml:space="preserve">
<value>Удалить</value>
</data>
<data name="Action_OpenOnCivitAi" xml:space="preserve">
<value>Открыть на CivitAI</value>
</data>
<data name="Label_ConnectedModel" xml:space="preserve">
<value>Связанная модель</value>
</data>
<data name="Label_LocalModel" xml:space="preserve">
<value>Локальная модель</value>
</data>
<data name="Action_ShowInExplorer" xml:space="preserve">
<value>Открыть в проводнике</value>
</data>
<data name="Action_New" xml:space="preserve">
<value>Новая...</value>
</data>
<data name="Label_Folder" xml:space="preserve">
<value>Папка</value>
</data>
<data name="Label_DropFileToImport" xml:space="preserve">
<value>Перетащите файл сюда для импорта</value>
</data>
<data name="Label_ImportAsConnected" xml:space="preserve">
<value>Импортировать как связанные</value>
</data>
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve">
<value>Искать связанные данные при новом локальном импорте</value>
</data>
<data name="Label_Indexing" xml:space="preserve">
<value>Индексирую...</value>
</data>
<data name="Label_ModelsFolder" xml:space="preserve">
<value>Папка с моделями</value>
</data>
<data name="Label_Categories" xml:space="preserve">
<value>Категории</value>
</data>
<data name="Label_LetsGetStarted" xml:space="preserve">
<value>Давайте приступим</value>
</data>
<data name="Label_ReadAndAgree" xml:space="preserve">
<value>Я прочёл и согласен с</value>
</data>
<data name="Label_LicenseAgreement" xml:space="preserve">
<value>Лицензионным соглашением.</value>
</data>
<data name="Label_FindConnectedMetadata" xml:space="preserve">
<value>Найти связанные данные</value>
</data>
<data name="Label_ShowModelImages" xml:space="preserve">
<value>Показать картинки моделей</value>
</data>
<data name="Label_Appearance" xml:space="preserve">
<value>Внешний вид</value>
</data>
<data name="Label_Theme" xml:space="preserve">
<value>Тема</value>
</data>
<data name="Label_CheckpointManager" xml:space="preserve">
<value>Управление моделями</value>
</data>
<data name="Label_RemoveSymlinksOnShutdown" xml:space="preserve">
<value>Удалить ссылки на расшаренные модели при выключении</value>
</data>
<data name="Label_RemoveSymlinksOnShutdown_Details" xml:space="preserve">
<value>Выберите эту настройку если у вас возникают проблемы с перемещением Stability Matrix на другой диск</value>
</data>
<data name="Label_ResetCheckpointsCache" xml:space="preserve">
<value>Сбросить кэш моделей</value>
</data>
<data name="Label_ResetCheckpointsCache_Details" xml:space="preserve">
<value>Перестраивает кэш установленных моделей. Используйте если модели некорректно названы в Просмотровщике моделей</value>
</data>
<data name="Label_PackageEnvironment" xml:space="preserve">
<value>Среда для пакетов</value>
</data>
<data name="Action_Edit" xml:space="preserve">
<value>Редактировать</value>
</data>
<data name="Label_EnvironmentVariables" xml:space="preserve">
<value>Переменные среды</value>
</data>
<data name="Label_EmbeddedPython" xml:space="preserve">
<value>Встроенный Python</value>
</data>
<data name="Action_CheckVersion" xml:space="preserve">
<value>Проверить версию</value>
</data>
<data name="Label_Integrations" xml:space="preserve">
<value>Интеграции</value>
</data>
<data name="Label_DiscordRichPresence" xml:space="preserve">
<value>Discord Rich Presence</value>
</data>
<data name="Label_System" xml:space="preserve">
<value>Система</value>
</data>
<data name="Label_AddToStartMenu" xml:space="preserve">
<value>Добавить ярлык Stability Matrix в меню &quot;Пуск&quot;</value>
</data>
<data name="Label_AddToStartMenu_Details" xml:space="preserve">
<value>Использует текущее местоположение приложения, вы можете запустить это снова, если переместите приложение</value>
</data>
<data name="Label_OnlyAvailableOnWindows" xml:space="preserve">
<value>Доступно только на Windows</value>
</data>
<data name="Action_AddForCurrentUser" xml:space="preserve">
<value>Добавить для Текущего пользователя</value>
</data>
<data name="Action_AddForAllUsers" xml:space="preserve">
<value>Добавить для всех</value>
</data>
<data name="Label_SelectNewDataDirectory" xml:space="preserve">
<value>Выберите новую папку с данными (&quot;Data&quot;)</value>
</data>
<data name="Label_SelectNewDataDirectory_Details" xml:space="preserve">
<value>Не перемещает существующие данные</value>
</data>
<data name="Action_SelectDirectory" xml:space="preserve">
<value>Выберите папку</value>
</data>
<data name="Label_About" xml:space="preserve">
<value>О программе</value>
</data>
<data name="Label_StabilityMatrix" xml:space="preserve">
<value>Stability Matrix</value>
</data>
<data name="Label_LicenseAndOpenSourceNotices" xml:space="preserve">
<value>Уведомления о лицензиях и открытом исходном коде</value>
</data>
<data name="TeachingTip_ClickLaunchToGetStarted" xml:space="preserve">
<value>Нажмите &quot;Запуск&quot; чтобы начать!</value>
</data>
<data name="Action_Stop" xml:space="preserve">
<value>Стоп</value>
</data>
<data name="Action_SendInput" xml:space="preserve">
<value>Послать команду</value>
</data>
<data name="Label_Input" xml:space="preserve">
<value>Команда</value>
</data>
<data name="Action_Send" xml:space="preserve">
<value>Отправить</value>
</data>
<data name="Label_InputRequired" xml:space="preserve">
<value>Требуется команда</value>
</data>
<data name="Label_ConfirmQuestion" xml:space="preserve">
<value>Подтвердить?</value>
</data>
<data name="Action_Yes" xml:space="preserve">
<value>Да</value>
</data>
<data name="Label_No" xml:space="preserve">
<value>Нет</value>
</data>
<data name="Action_OpenWebUI" xml:space="preserve">
<value>Открыть веб-интерфейс</value>
</data>
<data name="Text_WelcomeToStabilityMatrix" xml:space="preserve">
<value>Добро пожаловать в Stability Matrix!</value>
</data>
<data name="Text_OneClickInstaller_SubHeader" xml:space="preserve">
<value>Выберите желаемый интерфейс и нажмите &quot;Установить&quot; чтобы начать работу</value>
</data>
<data name="Label_Installing" xml:space="preserve">
<value>Устанавливаю</value>
</data>
<data name="Text_ProceedingToLaunchPage" xml:space="preserve">
<value>Перехожу на страницу запуска</value>
</data>
<data name="Progress_DownloadingPackage" xml:space="preserve">
<value>Скачиваю пакет...</value>
</data>
<data name="Progress_DownloadComplete" xml:space="preserve">
<value>Скачивание завершено</value>
</data>
<data name="Progress_InstallationComplete" xml:space="preserve">
<value>Установка завершена</value>
</data>
<data name="Progress_InstallingPrerequisites" xml:space="preserve">
<value>Устанавливаю необходимые компоненты...</value>
</data>
<data name="Progress_InstallingPackageRequirements" xml:space="preserve">
<value>Устанавливаю зависимости пакета...</value>
</data>
<data name="Action_OpenInExplorer" xml:space="preserve">
<value>Открыть в проводнике</value>
</data>
<data name="Action_OpenInFinder" xml:space="preserve">
<value>Открыть в файндере</value>
</data>
<data name="Action_Uninstall" xml:space="preserve">
<value>Удалить</value>
</data>
<data name="Action_CheckForUpdates" xml:space="preserve">
<value>Проверить обновления</value>
</data>
<data name="Action_Update" xml:space="preserve">
<value>Обновить</value>
</data>
<data name="Action_AddPackage" xml:space="preserve">
<value>Добавить пакет</value>
</data>
<data name="TeachingTip_AddPackageToGetStarted" xml:space="preserve">
<value>Добавьте пакет чтобы начать!</value>
</data>
<data name="Label_EnvVarsTable_Name" xml:space="preserve">
<value>Переменная</value>
</data>
<data name="Label_EnvVarsTable_Value" xml:space="preserve">
<value>Значение</value>
</data>
<data name="Action_Remove" xml:space="preserve">
<value>Удалить</value>
</data>
<data name="Label_Details" xml:space="preserve">
<value>Подробнее</value>
</data>
<data name="Label_Callstack" xml:space="preserve">
<value>Стэк вызовов (Callstack)</value>
</data>
<data name="Label_InnerException" xml:space="preserve">
<value>Внутреннее исключение (Inner exception)</value>
</data>
<data name="Label_SearchEllipsis" xml:space="preserve">
<value>Поиск...</value>
</data>
<data name="Action_OK" xml:space="preserve">
<value>ОК</value>
</data>
<data name="Action_Retry" xml:space="preserve">
<value>Повторить</value>
</data>
<data name="Label_PythonVersionInfo" xml:space="preserve">
<value>Версия Python</value>
</data>
<data name="Action_Restart" xml:space="preserve">
<value>Перезапустить</value>
</data>
<data name="Label_ConfirmDelete" xml:space="preserve">
<value>Подтвердите удаление</value>
</data>
<data name="Text_PackageUninstall_Details" xml:space="preserve">
<value>Это приведет к удалению папки пакета и всего её содержимого, включая любые сгенерированные картинки и файлы, которые вы, возможно, добавили.</value>
</data>
<data name="Progress_UninstallingPackage" xml:space="preserve">
<value>Удаляю пакет...</value>
</data>
<data name="Label_PackageUninstalled" xml:space="preserve">
<value>Пакет удалён</value>
</data>
<data name="Text_SomeFilesCouldNotBeDeleted" xml:space="preserve">
<value>Некоторые файлы не удалось удалить. Пожалуйста, закройте все открытые файлы в каталоге пакетов и повторите попытку.</value>
</data>
<data name="Label_InvalidPackageType" xml:space="preserve">
<value>Неправильный тип пакета</value>
</data>
<data name="TextTemplate_UpdatingPackage" xml:space="preserve">
<value>Обновляю {0}</value>
</data>
<data name="Progress_UpdateComplete" xml:space="preserve">
<value>Обновление завершено</value>
</data>
<data name="TextTemplate_PackageUpdatedToLatest" xml:space="preserve">
<value>{0} был обновлён до последней версии</value>
</data>
<data name="TextTemplate_ErrorUpdatingPackage" xml:space="preserve">
<value>Ошибка обновления {0}</value>
</data>
<data name="Progress_UpdateFailed" xml:space="preserve">
<value>Ошибка обновления</value>
</data>
<data name="Action_OpenInBrowser" xml:space="preserve">
<value>Открыть в браузере</value>
</data>
<data name="Label_ErrorInstallingPackage" xml:space="preserve">
<value>Ошибка установки пакета</value>
</data>
<data name="Label_Branch" xml:space="preserve">
<value>Ветка</value>
</data>
<data name="Label_AutoScrollToEnd" xml:space="preserve">
<value>Автоматически прокручивать до конца</value>
</data>
<data name="Label_License" xml:space="preserve">
<value>Лицензия</value>
</data>
<data name="Label_SharedModelStrategyShort" xml:space="preserve">
<value>Расшаривание моделей...</value>
</data>
<data name="Label_PleaseSelectDataDirectory" xml:space="preserve">
<value>Пожалуйста выберите папку с данными</value>
</data>
<data name="Label_DataFolderName" xml:space="preserve">
<value>Название папки с данными</value>
</data>
<data name="Label_CurrentDirectory" xml:space="preserve">
<value>Текущая папка:</value>
</data>
<data name="Text_AppWillRelaunchAfterUpdate" xml:space="preserve">
<value>Приложение перезапустится после обновления</value>
</data>
<data name="Action_RemindMeLater" xml:space="preserve">
<value>Напомнить позже</value>
</data>
<data name="Action_InstallNow" xml:space="preserve">
<value>Установить сейчас</value>
</data>
<data name="Label_ReleaseNotes" xml:space="preserve">
<value>Заметки о выпуске</value>
</data>
<data name="Action_OpenProjectEllipsis" xml:space="preserve">
<value>Открыть проект...</value>
</data>
<data name="Action_SaveAsEllipsis" xml:space="preserve">
<value>Сохранить как...</value>
</data>
<data name="Action_RestoreDefaultLayout" xml:space="preserve">
<value>Восстановить вид по умолчанию</value>
</data>
</root>

11
StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs

@ -66,17 +66,18 @@ public partial class ImageFolderCardViewModel : ViewModelBase
this.settingsManager = settingsManager;
this.notificationService = notificationService;
var predicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
var searcher = new ImageSearcher();
// Observable predicate from SearchQuery changes
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
.Throttle(TimeSpan.FromMilliseconds(50))!
.Select<PropertyValue<ImageFolderCardViewModel, string>, Func<LocalImageFile, bool>>(
p => file => SearchPredicate(file, p.Value)
)
.Select(property => searcher.GetPredicate(property.Value))
.AsObservable();
imageIndexService.InferenceImages.ItemsSource
.Connect()
.DeferUntilLoaded()
.Filter(predicate)
.Filter(searchPredicate)
.SortBy(file => file.LastModifiedAt, SortDirection.Descending)
.Bind(LocalImages)
.Subscribe();

48
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -19,6 +19,7 @@ using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Helpers;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
@ -26,6 +27,7 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
@ -53,8 +55,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
public SourceCache<OutputImageViewModel, string> OutputsCache { get; } =
new(p => p.ImageFile.AbsolutePath);
public SourceCache<LocalImageFile, string> OutputsCache { get; } =
new(file => file.AbsolutePath);
public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>();
@ -106,35 +108,20 @@ public partial class OutputsPageViewModel : PageViewModelBase
this.navigationService = navigationService;
this.logger = logger;
var predicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
var searcher = new ImageSearcher();
// Observable predicate from SearchQuery changes
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
.Throttle(TimeSpan.FromMilliseconds(50))!
.Select<PropertyValue<OutputsPageViewModel, string>, Func<OutputImageViewModel, bool>>(
propertyValue =>
output =>
{
if (string.IsNullOrWhiteSpace(propertyValue.Value))
return true;
return output.ImageFile.FileName.Contains(
propertyValue.Value,
StringComparison.OrdinalIgnoreCase
)
|| (
output.ImageFile.GenerationParameters?.PositivePrompt != null
&& output.ImageFile.GenerationParameters.PositivePrompt.Contains(
propertyValue.Value,
StringComparison.OrdinalIgnoreCase
)
);
}
)
.Select(property => searcher.GetPredicate(property.Value))
.AsObservable();
OutputsCache
.Connect()
.DeferUntilLoaded()
.Filter(predicate)
.SortBy(x => x.ImageFile.CreatedAt, SortDirection.Descending)
.Filter(searchPredicate)
.SortBy(file => file.CreatedAt, SortDirection.Descending)
.Transform(file => new OutputImageViewModel(file))
.Bind(Outputs)
.WhenPropertyChanged(p => p.IsSelected)
.Subscribe(_ =>
@ -320,7 +307,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
return;
}
OutputsCache.Remove(item);
OutputsCache.Remove(item.ImageFile);
// Invalidate cache
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
@ -384,7 +371,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
{
continue;
}
OutputsCache.Remove(output);
OutputsCache.Remove(output.ImageFile);
// Invalidate cache
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
@ -482,11 +469,10 @@ public partial class OutputsPageViewModel : PageViewModelBase
return;
}
var list = Directory
var files = Directory
.EnumerateFiles(directory, "*.png", SearchOption.AllDirectories)
.Select(file => new OutputImageViewModel(LocalImageFile.FromPath(file)))
.OrderByDescending(f => f.ImageFile.CreatedAt);
.Select(file => LocalImageFile.FromPath(file));
OutputsCache.EditDiff(list, (x, y) => x.ImageFile.AbsolutePath == y.ImageFile.AbsolutePath);
OutputsCache.EditDiff(files);
}
}

9
StabilityMatrix.Avalonia/Views/SettingsPage.axaml

@ -1,4 +1,4 @@
<controls:UserControlBase
<controls:UserControlBase
x:Class="StabilityMatrix.Avalonia.Views.SettingsPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -11,6 +11,7 @@
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
@ -21,6 +22,10 @@
x:CompileBindings="True"
x:DataType="vm:SettingsViewModel"
mc:Ignorable="d">
<controls:UserControlBase.Resources>
<converters:CultureInfoDisplayConverter x:Key="CultureInfoDisplayConverter" />
</controls:UserControlBase.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="8,16" Spacing="8">
@ -51,7 +56,7 @@
<ComboBox
MinWidth="100"
ItemsSource="{Binding AvailableLanguages}"
DisplayMemberBinding="{Binding NativeName}"
DisplayMemberBinding="{Binding Converter={StaticResource CultureInfoDisplayConverter}}"
SelectedItem="{Binding SelectedLanguage}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>

35
StabilityMatrix.Core/Extensions/DynamicDataExtensions.cs

@ -0,0 +1,35 @@
using DynamicData;
namespace StabilityMatrix.Core.Extensions;
public static class DynamicDataExtensions
{
/// <summary>
/// Loads the cache with the specified items in an optimised manner i.e. calculates the differences between the old and new items
/// in the list and amends only the differences.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="source">The source.</param>
/// <param name="allItems">The items to add, update or delete.</param>
/// <exception cref="System.ArgumentNullException">source.</exception>
public static void EditDiff<TObject, TKey>(
this ISourceCache<TObject, TKey> source,
IEnumerable<TObject> allItems
)
where TObject : IEquatable<TObject>
where TKey : notnull
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
if (allItems is null)
{
throw new ArgumentNullException(nameof(allItems));
}
source.EditDiff(allItems, (x, y) => x.Equals(y));
}
}

40
StabilityMatrix.Core/Models/Database/LocalImageFile.cs

@ -1,4 +1,4 @@
using LiteDB;
using DynamicData.Tests;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.FileInterfaces;
using JsonSerializer = System.Text.Json.JsonSerializer;
@ -9,7 +9,7 @@ namespace StabilityMatrix.Core.Models.Database;
/// <summary>
/// Represents a locally indexed image file.
/// </summary>
public class LocalImageFile
public record LocalImageFile
{
public required string AbsolutePath { get; init; }
@ -117,40 +117,4 @@ public class LocalImageFile
public static readonly HashSet<string> SupportedImageExtensions =
new() { ".png", ".jpg", ".jpeg", ".webp" };
private sealed class LocalImageFileEqualityComparer : IEqualityComparer<LocalImageFile>
{
public bool Equals(LocalImageFile? x, LocalImageFile? y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.GetType() != y.GetType())
return false;
return x.AbsolutePath == y.AbsolutePath
&& x.ImageType == y.ImageType
&& x.CreatedAt.Equals(y.CreatedAt)
&& x.LastModifiedAt.Equals(y.LastModifiedAt)
&& Equals(x.GenerationParameters, y.GenerationParameters)
&& Nullable.Equals(x.ImageSize, y.ImageSize);
}
public int GetHashCode(LocalImageFile obj)
{
return HashCode.Combine(
obj.AbsolutePath,
obj.ImageType,
obj.CreatedAt,
obj.LastModifiedAt,
obj.GenerationParameters,
obj.ImageSize
);
}
}
public static IEqualityComparer<LocalImageFile> Comparer { get; } =
new LocalImageFileEqualityComparer();
}

3
StabilityMatrix.Core/Models/FileInterfaces/FilePath.cs

@ -33,6 +33,9 @@ public class FilePath : FileSystemPath, IPathObject
[JsonIgnore]
public string NameWithoutExtension => Path.GetFileNameWithoutExtension(Info.Name);
[JsonIgnore]
public string Extension => Info.Extension;
/// <summary>
/// Get the directory of the file.
/// </summary>

80
StabilityMatrix.Core/Models/GenerationParameters.cs

@ -1,12 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using StabilityMatrix.Core.Models.Api.Comfy;
namespace StabilityMatrix.Core.Models;
[JsonSerializable(typeof(GenerationParameters))]
public partial record GenerationParameters
public record GenerationParameters
{
public string? PositivePrompt { get; set; }
public string? NegativePrompt { get; set; }
@ -51,43 +50,68 @@ public partial record GenerationParameters
}
// Join lines before last line, split at 'Negative prompt: '
var joinedLines = string.Join("\n", lines[..^1]);
var joinedLines = string.Join("\n", lines[..^1]).Trim();
var splitFirstPart = joinedLines.Split("Negative prompt: ");
if (splitFirstPart.Length != 2)
{
generationParameters = null;
return false;
}
var splitFirstPart = joinedLines.Split("Negative prompt: ", 2);
var positivePrompt = splitFirstPart[0];
var negativePrompt = splitFirstPart[1];
var positivePrompt = splitFirstPart.ElementAtOrDefault(0)?.Trim();
var negativePrompt = splitFirstPart.ElementAtOrDefault(1)?.Trim();
// Parse last line
var match = ParseLastLineRegex().Match(lastLine);
if (!match.Success)
{
generationParameters = null;
return false;
}
var lineFields = ParseLine(lastLine);
generationParameters = new GenerationParameters
{
PositivePrompt = positivePrompt,
NegativePrompt = negativePrompt,
Steps = int.Parse(match.Groups["Steps"].Value),
Sampler = match.Groups["Sampler"].Value,
CfgScale = double.Parse(match.Groups["CfgScale"].Value),
Seed = ulong.Parse(match.Groups["Seed"].Value),
Height = int.Parse(match.Groups["Height"].Value),
Width = int.Parse(match.Groups["Width"].Value),
ModelHash = match.Groups["ModelHash"].Value,
ModelName = match.Groups["ModelName"].Value,
Steps = int.Parse(lineFields.GetValueOrDefault("Steps", "0")),
Sampler = lineFields.GetValueOrDefault("Sampler"),
CfgScale = double.Parse(lineFields.GetValueOrDefault("CFG scale", "0")),
Seed = ulong.Parse(lineFields.GetValueOrDefault("Seed", "0")),
ModelHash = lineFields.GetValueOrDefault("Model hash"),
ModelName = lineFields.GetValueOrDefault("Model"),
};
if (lineFields.GetValueOrDefault("Size") is { } size)
{
var split = size.Split('x', 2);
if (split.Length == 2)
{
generationParameters = generationParameters with
{
Width = int.Parse(split[0]),
Height = int.Parse(split[1])
};
}
}
return true;
}
/// <summary>
/// Parse A1111 metadata fields in a single line where
/// fields are separated by commas and key-value pairs are separated by colons.
/// i.e. "key1: value1, key2: value2"
/// </summary>
internal static Dictionary<string, string> ParseLine(string fields)
{
var dict = new Dictionary<string, string>();
foreach (var field in fields.Split(','))
{
var split = field.Split(':', 2);
if (split.Length < 2)
continue;
var key = split[0].Trim();
var value = split[1].Trim();
dict.Add(key, value);
}
return dict;
}
/// <summary>
/// Converts current <see cref="Sampler"/> string to <see cref="ComfySampler"/> and <see cref="ComfyScheduler"/>.
/// </summary>
@ -145,10 +169,4 @@ public partial record GenerationParameters
Sampler = "DPM++ 2M Karras"
};
}
// Example: Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1
[GeneratedRegex(
"""^Steps: (?<Steps>\d+), Sampler: (?<Sampler>.+?), CFG scale: (?<CfgScale>\d+(\.\d+)?), Seed: (?<Seed>\d+), Size: (?<Width>\d+)x(?<Height>\d+), Model hash: (?<ModelHash>.+?), Model: (?<ModelName>.+)$"""
)]
private static partial Regex ParseLastLineRegex();
}

10
StabilityMatrix.Core/Services/ImageIndexService.cs

@ -4,6 +4,7 @@ using AsyncAwaitBestPractices;
using DynamicData;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
@ -61,10 +62,11 @@ public class ImageIndexService : IImageIndexService
await Task.Run(() =>
{
var files = imagesDir.Info
var files = imagesDir
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.Where(info => LocalImageFile.SupportedImageExtensions.Contains(info.Extension))
.Select(info => new FilePath(info));
.Where(
file => LocalImageFile.SupportedImageExtensions.Contains(file.Extension)
);
Parallel.ForEach(
files,
@ -78,7 +80,7 @@ public class ImageIndexService : IImageIndexService
var indexElapsed = stopwatch.Elapsed;
indexCollection.ItemsSource.EditDiff(toAdd, LocalImageFile.Comparer);
indexCollection.ItemsSource.EditDiff(toAdd);
// End
stopwatch.Stop();

70
StabilityMatrix.Tests/Models/GenerationParametersTests.cs

@ -0,0 +1,70 @@
using StabilityMatrix.Core.Models;
namespace StabilityMatrix.Tests.Models;
[TestClass]
public class GenerationParametersTests
{
[TestMethod]
public void TestParse()
{
const string data = """
test123
Negative prompt: test, easy negative
Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 3589107295, Size: 1024x1028, Model hash: 9aa0c3e54d, Model: nightvisionXL_v0770_BakedVAE, VAE hash: 235745af8d, VAE: sdxl_vae.safetensors, Style Selector Enabled: True, Style Selector Randomize: False, Style Selector Style: base, Version: 1.6.0
""";
Assert.IsTrue(GenerationParameters.TryParse(data, out var result));
Assert.AreEqual("test123", result.PositivePrompt);
Assert.AreEqual("test, easy negative", result.NegativePrompt);
Assert.AreEqual(20, result.Steps);
Assert.AreEqual("Euler a", result.Sampler);
Assert.AreEqual(7, result.CfgScale);
Assert.AreEqual(3589107295, result.Seed);
Assert.AreEqual(1024, result.Width);
Assert.AreEqual(1028, result.Height);
Assert.AreEqual("9aa0c3e54d", result.ModelHash);
Assert.AreEqual("nightvisionXL_v0770_BakedVAE", result.ModelName);
}
[TestMethod]
public void TestParse_NoNegative()
{
const string data = """
test123
Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 3589107295, Size: 1024x1028, Model hash: 9aa0c3e54d, Model: nightvisionXL_v0770_BakedVAE, VAE hash: 235745af8d, VAE: sdxl_vae.safetensors, Style Selector Enabled: True, Style Selector Randomize: False, Style Selector Style: base, Version: 1.6.0
""";
Assert.IsTrue(GenerationParameters.TryParse(data, out var result));
Assert.AreEqual("test123", result.PositivePrompt);
Assert.IsNull(result.NegativePrompt);
Assert.AreEqual(20, result.Steps);
Assert.AreEqual("Euler a", result.Sampler);
Assert.AreEqual(7, result.CfgScale);
Assert.AreEqual(3589107295, result.Seed);
Assert.AreEqual(1024, result.Width);
Assert.AreEqual(1028, result.Height);
Assert.AreEqual("9aa0c3e54d", result.ModelHash);
Assert.AreEqual("nightvisionXL_v0770_BakedVAE", result.ModelName);
}
[TestMethod]
public void TestParseLineFields()
{
const string lastLine =
@"Steps: 30, Sampler: DPM++ 2M Karras, CFG scale: 7, Seed: 2216407431, Size: 640x896, Model hash: eb2h052f91, Model: anime_v1";
var fields = GenerationParameters.ParseLine(lastLine);
Assert.AreEqual(7, fields.Count);
Assert.AreEqual("30", fields["Steps"]);
Assert.AreEqual("DPM++ 2M Karras", fields["Sampler"]);
Assert.AreEqual("7", fields["CFG scale"]);
Assert.AreEqual("2216407431", fields["Seed"]);
Assert.AreEqual("640x896", fields["Size"]);
Assert.AreEqual("eb2h052f91", fields["Model hash"]);
Assert.AreEqual("anime_v1", fields["Model"]);
}
}
Loading…
Cancel
Save