diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index efb0216f..23ffdba3 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -662,6 +662,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Auto Completion.
+ ///
+ public static string Label_AutoCompletion {
+ get {
+ return ResourceManager.GetString("Label_AutoCompletion", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Automatically scroll to end of console output.
///
@@ -797,6 +806,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Replace underscores with spaces when inserting completions.
+ ///
+ public static string Label_CompletionReplaceUnderscoresWithSpaces {
+ get {
+ return ResourceManager.GetString("Label_CompletionReplaceUnderscoresWithSpaces", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Confirm Delete.
///
@@ -1094,6 +1112,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to General.
+ ///
+ public static string Label_General {
+ get {
+ return ResourceManager.GetString("Label_General", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Height.
///
@@ -1112,6 +1139,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Image Viewer.
+ ///
+ public static string Label_ImageViewer {
+ get {
+ return ResourceManager.GetString("Label_ImageViewer", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Import with Metadata.
///
@@ -1148,6 +1184,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Inference.
+ ///
+ public static string Label_Inference {
+ get {
+ return ResourceManager.GetString("Label_Inference", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Inner exception.
///
@@ -1418,6 +1463,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Output Image Files.
+ ///
+ public static string Label_OutputImageFiles {
+ get {
+ return ResourceManager.GetString("Label_OutputImageFiles", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Output Browser.
///
@@ -1535,6 +1589,42 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
+ ///
+ /// Looks up a localized string similar to Prompt.
+ ///
+ public static string Label_Prompt {
+ get {
+ return ResourceManager.GetString("Label_Prompt", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Prompt Tags.
+ ///
+ public static string Label_PromptTags {
+ get {
+ return ResourceManager.GetString("Label_PromptTags", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format).
+ ///
+ public static string Label_PromptTagsDescription {
+ get {
+ return ResourceManager.GetString("Label_PromptTagsDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Import Prompt tags.
+ ///
+ public static string Label_PromptTagsImport {
+ get {
+ return ResourceManager.GetString("Label_PromptTagsImport", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Python Packages.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 76da7ad6..be55b0f1 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -813,4 +813,34 @@
Additional folders such as IPAdapters and TextualInversions (embeddings) can be enabled here
+
+ GeneralA general settings category
+
+
+ InferenceThe Inference feature page
+
+
+ PromptA settings category for Inference generation prompts
+
+
+ Output Image Files
+
+
+ Image Viewer
+
+
+ Auto Completion
+
+
+ Replace underscores with spaces when inserting completions
+
+
+ Prompt TagsTags for image generation prompts
+
+
+ Import Prompt tags
+
+
+ Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format)
+
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
index 4ce798cf..9e3b79ce 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/InferenceSettingsViewModel.cs
@@ -1,7 +1,28 @@
-using FluentAvalonia.UI.Controls;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DynamicData.Binding;
+using FluentAvalonia.UI.Controls;
+using NLog;
+using StabilityMatrix.Avalonia.Extensions;
+using StabilityMatrix.Avalonia.Models.Inference;
+using StabilityMatrix.Avalonia.Models.TagCompletion;
+using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.Settings;
using StabilityMatrix.Core.Attributes;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Python;
+using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
@@ -9,12 +30,202 @@ namespace StabilityMatrix.Avalonia.ViewModels.Settings;
[View(typeof(InferenceSettingsPage))]
[Singleton, ManagedService]
-public class InferenceSettingsViewModel : PageViewModelBase
+public partial class InferenceSettingsViewModel : PageViewModelBase
{
+ private readonly INotificationService notificationService;
+ private readonly ISettingsManager settingsManager;
+ private readonly ICompletionProvider completionProvider;
+
///
public override string Title => "Inference";
///
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true };
+
+ [ObservableProperty]
+ private bool isPromptCompletionEnabled = true;
+
+ [ObservableProperty]
+ private IReadOnlyList availableTagCompletionCsvs = Array.Empty();
+
+ [ObservableProperty]
+ private string? selectedTagCompletionCsv;
+
+ [ObservableProperty]
+ private bool isCompletionRemoveUnderscoresEnabled = true;
+
+ [ObservableProperty]
+ [CustomValidation(typeof(InferenceSettingsViewModel), nameof(ValidateOutputImageFileNameFormat))]
+ private string? outputImageFileNameFormat;
+
+ [ObservableProperty]
+ private string? outputImageFileNameFormatSample;
+
+ public IEnumerable OutputImageFileNameFormatVars =>
+ FileNameFormatProvider
+ .GetSample()
+ .Substitutions.Select(
+ kv =>
+ new FileNameFormatVar
+ {
+ Variable = $"{{{kv.Key}}}",
+ Example = kv.Value.Invoke()
+ }
+ );
+
+ [ObservableProperty]
+ private bool isImageViewerPixelGridEnabled = true;
+
+ public InferenceSettingsViewModel(INotificationService notificationService, IPrerequisiteHelper prerequisiteHelper, IPyRunner pyRunner, ServiceManager dialogFactory, ICompletionProvider completionProvider, ITrackedDownloadService trackedDownloadService, IModelIndexService modelIndexService, INavigationService settingsNavigationService, IAccountsService accountsService, ISettingsManager settingsManager)
+ {
+ this.settingsManager = settingsManager;
+ this.notificationService = notificationService;
+ this.completionProvider = completionProvider;
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.SelectedTagCompletionCsv,
+ settings => settings.TagCompletionCsv
+ );
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.IsPromptCompletionEnabled,
+ settings => settings.IsPromptCompletionEnabled,
+ true
+ );
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.IsCompletionRemoveUnderscoresEnabled,
+ settings => settings.IsCompletionRemoveUnderscoresEnabled,
+ true
+ );
+
+ this.WhenPropertyChanged(vm => vm.OutputImageFileNameFormat)
+ .Throttle(TimeSpan.FromMilliseconds(50))
+ .Subscribe(formatProperty =>
+ {
+ var provider = FileNameFormatProvider.GetSample();
+ var template = formatProperty.Value ?? string.Empty;
+
+ if (
+ !string.IsNullOrEmpty(template)
+ && provider.Validate(template) == ValidationResult.Success
+ )
+ {
+ var format = FileNameFormat.Parse(template, provider);
+ OutputImageFileNameFormatSample = format.GetFileName() + ".png";
+ }
+ else
+ {
+ // Use default format if empty
+ var defaultFormat = FileNameFormat.Parse(
+ FileNameFormat.DefaultTemplate,
+ provider
+ );
+ OutputImageFileNameFormatSample = defaultFormat.GetFileName() + ".png";
+ }
+ });
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.OutputImageFileNameFormat,
+ settings => settings.InferenceOutputImageFileNameFormat,
+ true
+ );
+
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.IsImageViewerPixelGridEnabled,
+ settings => settings.IsImageViewerPixelGridEnabled,
+ true
+ );
+
+ ImportTagCsvCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
+ }
+
+ ///
+ /// Validator for
+ ///
+ public static ValidationResult ValidateOutputImageFileNameFormat(
+ string? format,
+ ValidationContext context
+ )
+ {
+ return FileNameFormatProvider.GetSample().Validate(format ?? string.Empty);
+ }
+
+ ///
+ public override void OnLoaded()
+ {
+ base.OnLoaded();
+
+ UpdateAvailableTagCompletionCsvs();
+ }
+
+ #region Commands
+
+ [RelayCommand(FlowExceptionsToTaskScheduler = true)]
+ private async Task ImportTagCsv()
+ {
+ var storage = App.StorageProvider;
+ var files = await storage.OpenFilePickerAsync(
+ new FilePickerOpenOptions
+ {
+ FileTypeFilter = new List
+ {
+ new("CSV")
+ {
+ Patterns = ["*.csv"]
+ }
+ }
+ }
+ );
+
+ if (files.Count == 0)
+ return;
+
+ var sourceFile = new FilePath(files[0].TryGetLocalPath()!);
+
+ var tagsDir = settingsManager.TagsDirectory;
+ tagsDir.Create();
+
+ // Copy to tags directory
+ var targetFile = tagsDir.JoinFile(sourceFile.Name);
+ await sourceFile.CopyToAsync(targetFile);
+
+ // Update index
+ UpdateAvailableTagCompletionCsvs();
+
+ // Trigger load
+ completionProvider.BackgroundLoadFromFile(targetFile, true);
+
+ notificationService.Show(
+ $"Imported {sourceFile.Name}",
+ $"The {sourceFile.Name} file has been imported.",
+ NotificationType.Success
+ );
+ }
+ #endregion
+
+ private void UpdateAvailableTagCompletionCsvs()
+ {
+ if (!settingsManager.IsLibraryDirSet)
+ return;
+
+ if (settingsManager.TagsDirectory is not { Exists: true } tagsDir)
+ return;
+
+ var csvFiles = tagsDir.Info.EnumerateFiles("*.csv");
+ AvailableTagCompletionCsvs = csvFiles.Select(f => f.Name).ToImmutableArray();
+
+ // Set selected to current if exists
+ var settingsCsv = settingsManager.Settings.TagCompletionCsv;
+ if (settingsCsv is not null && AvailableTagCompletionCsvs.Contains(settingsCsv))
+ {
+ SelectedTagCompletionCsv = settingsCsv;
+ }
+ }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index 8ef5c63f..47408a22 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -102,41 +102,6 @@ public partial class MainSettingsViewModel : PageViewModelBase
[ObservableProperty]
private bool removeSymlinksOnShutdown;
- // Inference UI section
- [ObservableProperty]
- private bool isPromptCompletionEnabled = true;
-
- [ObservableProperty]
- private IReadOnlyList availableTagCompletionCsvs = Array.Empty();
-
- [ObservableProperty]
- private string? selectedTagCompletionCsv;
-
- [ObservableProperty]
- private bool isCompletionRemoveUnderscoresEnabled = true;
-
- [ObservableProperty]
- [CustomValidation(typeof(MainSettingsViewModel), nameof(ValidateOutputImageFileNameFormat))]
- private string? outputImageFileNameFormat;
-
- [ObservableProperty]
- private string? outputImageFileNameFormatSample;
-
- public IEnumerable OutputImageFileNameFormatVars =>
- FileNameFormatProvider
- .GetSample()
- .Substitutions.Select(
- kv =>
- new FileNameFormatVar
- {
- Variable = $"{{{kv.Key}}}",
- Example = kv.Value.Invoke()
- }
- );
-
- [ObservableProperty]
- private bool isImageViewerPixelGridEnabled = true;
-
// Integrations section
[ObservableProperty]
private bool isDiscordRichPresenceEnabled;
@@ -217,71 +182,10 @@ public partial class MainSettingsViewModel : PageViewModelBase
settings => settings.AnimationScale
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.SelectedTagCompletionCsv,
- settings => settings.TagCompletionCsv
- );
-
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.IsPromptCompletionEnabled,
- settings => settings.IsPromptCompletionEnabled,
- true
- );
-
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.IsCompletionRemoveUnderscoresEnabled,
- settings => settings.IsCompletionRemoveUnderscoresEnabled,
- true
- );
-
- this.WhenPropertyChanged(vm => vm.OutputImageFileNameFormat)
- .Throttle(TimeSpan.FromMilliseconds(50))
- .Subscribe(formatProperty =>
- {
- var provider = FileNameFormatProvider.GetSample();
- var template = formatProperty.Value ?? string.Empty;
-
- if (
- !string.IsNullOrEmpty(template)
- && provider.Validate(template) == ValidationResult.Success
- )
- {
- var format = FileNameFormat.Parse(template, provider);
- OutputImageFileNameFormatSample = format.GetFileName() + ".png";
- }
- else
- {
- // Use default format if empty
- var defaultFormat = FileNameFormat.Parse(
- FileNameFormat.DefaultTemplate,
- provider
- );
- OutputImageFileNameFormatSample = defaultFormat.GetFileName() + ".png";
- }
- });
-
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.OutputImageFileNameFormat,
- settings => settings.InferenceOutputImageFileNameFormat,
- true
- );
-
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.IsImageViewerPixelGridEnabled,
- settings => settings.IsImageViewerPixelGridEnabled,
- true
- );
-
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(
notificationService,
LogLevel.Warn
);
- ImportTagCsvCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
}
///
@@ -291,20 +195,10 @@ public partial class MainSettingsViewModel : PageViewModelBase
await notificationService.TryAsync(completionProvider.Setup());
- UpdateAvailableTagCompletionCsvs();
-
// Start accounts update
accountsService.RefreshAsync().SafeFireAndForget();
}
- public static ValidationResult ValidateOutputImageFileNameFormat(
- string? format,
- ValidationContext context
- )
- {
- return FileNameFormatProvider.GetSample().Validate(format ?? string.Empty);
- }
-
partial void OnSelectedThemeChanged(string? value)
{
// In case design / tests
@@ -453,68 +347,6 @@ public partial class MainSettingsViewModel : PageViewModelBase
#endregion
- #region Inference UI
-
- private void UpdateAvailableTagCompletionCsvs()
- {
- if (!settingsManager.IsLibraryDirSet)
- return;
-
- var tagsDir = settingsManager.TagsDirectory;
- if (!tagsDir.Exists)
- return;
-
- var csvFiles = tagsDir.Info.EnumerateFiles("*.csv");
- AvailableTagCompletionCsvs = csvFiles.Select(f => f.Name).ToImmutableArray();
-
- // Set selected to current if exists
- var settingsCsv = settingsManager.Settings.TagCompletionCsv;
- if (settingsCsv is not null && AvailableTagCompletionCsvs.Contains(settingsCsv))
- {
- SelectedTagCompletionCsv = settingsCsv;
- }
- }
-
- [RelayCommand(FlowExceptionsToTaskScheduler = true)]
- private async Task ImportTagCsv()
- {
- var storage = App.StorageProvider;
- var files = await storage.OpenFilePickerAsync(
- new FilePickerOpenOptions
- {
- FileTypeFilter = new List
- {
- new("CSV") { Patterns = new[] { "*.csv" }, }
- }
- }
- );
-
- if (files.Count == 0)
- return;
-
- var sourceFile = new FilePath(files[0].TryGetLocalPath()!);
-
- var tagsDir = settingsManager.TagsDirectory;
- tagsDir.Create();
-
- // Copy to tags directory
- var targetFile = tagsDir.JoinFile(sourceFile.Name);
- await sourceFile.CopyToAsync(targetFile);
-
- // Update index
- UpdateAvailableTagCompletionCsvs();
-
- // Trigger load
- completionProvider.BackgroundLoadFromFile(targetFile, true);
-
- notificationService.Show(
- $"Imported {sourceFile.Name}",
- $"The {sourceFile.Name} file has been imported.",
- NotificationType.Success
- );
- }
- #endregion
-
#region System
///
diff --git a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
index a7fdf1a8..da966284 100644
--- a/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
@@ -24,7 +24,7 @@ public partial class SettingsViewModel : PageViewModelBase
public IReadOnlyList SubPages { get; }
[ObservableProperty]
- private ObservableCollection currentPagePath = new();
+ private ObservableCollection currentPagePath = [];
[ObservableProperty]
private PageViewModelBase? currentPage;
diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
index a5abbfc3..9eb69ab6 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml
@@ -4,66 +4,144 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
+ xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
+ xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings"
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}"
- d:DesignHeight="450"
- d:DesignWidth="800"
+ d:DesignHeight="650"
+ d:DesignWidth="900"
x:DataType="vmSettings:InferenceSettingsViewModel"
mc:Ignorable="d">
+
+
+
+
+
-
-
+
+
+
+ Text="{x:Static lang:Resources.Label_Prompt}" />
+
-
-
-
-
-
+ Margin="8,0"
+ Header="{x:Static lang:Resources.Label_AutoCompletion}">
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
+
+
+ Text="{x:Static lang:Resources.Label_General}" />
+
+
-
-
-
+ Header="{x:Static lang:Resources.Label_ImageViewer}"
+ IconSource="Image"
+ IsExpanded="True">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
index 04be95a7..52da78bb 100644
--- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml
@@ -104,112 +104,30 @@
-
-
-
+
+
+
-
+ Text="{x:Static lang:Resources.Label_General}" />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Margin="8,0"
+ ActionIconSource="ChevronRight"
+ Command="{Binding NavigateToSubPageCommand}"
+ CommandParameter="{x:Type vmSettings:InferenceSettingsViewModel}"
+ Header="{x:Static lang:Resources.Label_Inference}"
+ IsClickEnabled="True">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml
index db6a5b5c..a347b077 100644
--- a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml
@@ -19,10 +19,13 @@
mc:Ignorable="d">
+
+
24
17
6,3
Medium
+