Ionite
1 year ago
23 changed files with 1813 additions and 1459 deletions
@ -0,0 +1,10 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Models; |
||||||
|
|
||||||
|
public class TypedNavigationEventArgs : EventArgs |
||||||
|
{ |
||||||
|
public required Type ViewModelType { get; init; } |
||||||
|
|
||||||
|
public object? ViewModel { get; init; } |
||||||
|
} |
@ -1,9 +1,20 @@ |
|||||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
using FluentAvalonia.UI.Controls; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
using StabilityMatrix.Avalonia.Views.Settings; |
using StabilityMatrix.Avalonia.Views.Settings; |
||||||
using StabilityMatrix.Core.Attributes; |
using StabilityMatrix.Core.Attributes; |
||||||
|
using Symbol = FluentIcons.Common.Symbol; |
||||||
|
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; |
||||||
|
|
||||||
namespace StabilityMatrix.Avalonia.ViewModels.Settings; |
namespace StabilityMatrix.Avalonia.ViewModels.Settings; |
||||||
|
|
||||||
[View(typeof(InferenceSettingsPage))] |
[View(typeof(InferenceSettingsPage))] |
||||||
[Singleton] |
[Singleton, ManagedService] |
||||||
public partial class InferenceSettingsViewModel : ViewModelBase { } |
public class InferenceSettingsViewModel : PageViewModelBase |
||||||
|
{ |
||||||
|
/// <inheritdoc /> |
||||||
|
public override string Title => "Inference"; |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override IconSource IconSource => |
||||||
|
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; |
||||||
|
} |
||||||
|
@ -0,0 +1,950 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.Immutable; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.ComponentModel.DataAnnotations; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Globalization; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Reactive.Linq; |
||||||
|
using System.Reflection; |
||||||
|
using System.Text; |
||||||
|
using System.Text.Json; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Avalonia; |
||||||
|
using Avalonia.Controls.Notifications; |
||||||
|
using Avalonia.Controls.Primitives; |
||||||
|
using Avalonia.Media.Imaging; |
||||||
|
using Avalonia.Platform.Storage; |
||||||
|
using Avalonia.Styling; |
||||||
|
using Avalonia.Threading; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using DynamicData.Binding; |
||||||
|
using FluentAvalonia.UI.Controls; |
||||||
|
using FluentAvalonia.UI.Media.Animation; |
||||||
|
using NLog; |
||||||
|
using SkiaSharp; |
||||||
|
using StabilityMatrix.Avalonia.Animations; |
||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Avalonia.Extensions; |
||||||
|
using StabilityMatrix.Avalonia.Helpers; |
||||||
|
using StabilityMatrix.Avalonia.Languages; |
||||||
|
using StabilityMatrix.Avalonia.Models; |
||||||
|
using StabilityMatrix.Avalonia.Models.Inference; |
||||||
|
using StabilityMatrix.Avalonia.Models.TagCompletion; |
||||||
|
using StabilityMatrix.Avalonia.Services; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||||
|
using StabilityMatrix.Avalonia.ViewModels.Inference; |
||||||
|
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||||
|
using StabilityMatrix.Avalonia.Views.Settings; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
using StabilityMatrix.Core.Extensions; |
||||||
|
using StabilityMatrix.Core.Helper; |
||||||
|
using StabilityMatrix.Core.Models; |
||||||
|
using StabilityMatrix.Core.Models.FileInterfaces; |
||||||
|
using StabilityMatrix.Core.Python; |
||||||
|
using StabilityMatrix.Core.Services; |
||||||
|
using Symbol = FluentIcons.Common.Symbol; |
||||||
|
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.ViewModels.Settings; |
||||||
|
|
||||||
|
[View(typeof(MainSettingsPage))] |
||||||
|
[Singleton, ManagedService] |
||||||
|
public partial class MainSettingsViewModel : PageViewModelBase |
||||||
|
{ |
||||||
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||||
|
|
||||||
|
private readonly INotificationService notificationService; |
||||||
|
private readonly ISettingsManager settingsManager; |
||||||
|
private readonly IPrerequisiteHelper prerequisiteHelper; |
||||||
|
private readonly IPyRunner pyRunner; |
||||||
|
private readonly ServiceManager<ViewModelBase> dialogFactory; |
||||||
|
private readonly ICompletionProvider completionProvider; |
||||||
|
private readonly ITrackedDownloadService trackedDownloadService; |
||||||
|
private readonly IModelIndexService modelIndexService; |
||||||
|
private readonly INavigationService<SettingsViewModel> settingsNavigationService; |
||||||
|
|
||||||
|
public SharedState SharedState { get; } |
||||||
|
|
||||||
|
public override string Title => "Settings"; |
||||||
|
public override IconSource IconSource => |
||||||
|
new SymbolIconSource { Symbol = Symbol.Settings, IsFilled = true }; |
||||||
|
|
||||||
|
// ReSharper disable once MemberCanBeMadeStatic.Global |
||||||
|
public string AppVersion => |
||||||
|
$"Version {Compat.AppVersion}" + (Program.IsDebugBuild ? " (Debug)" : ""); |
||||||
|
|
||||||
|
// Theme section |
||||||
|
[ObservableProperty] |
||||||
|
private string? selectedTheme; |
||||||
|
|
||||||
|
public IReadOnlyList<string> AvailableThemes { get; } = new[] { "Light", "Dark", "System", }; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private CultureInfo selectedLanguage; |
||||||
|
|
||||||
|
// ReSharper disable once MemberCanBeMadeStatic.Global |
||||||
|
public IReadOnlyList<CultureInfo> AvailableLanguages => Cultures.SupportedCultures; |
||||||
|
|
||||||
|
public IReadOnlyList<float> AnimationScaleOptions { get; } = |
||||||
|
new[] { 0f, 0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f, }; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private float selectedAnimationScale; |
||||||
|
|
||||||
|
// Shared folder options |
||||||
|
[ObservableProperty] |
||||||
|
private bool removeSymlinksOnShutdown; |
||||||
|
|
||||||
|
// Inference UI section |
||||||
|
[ObservableProperty] |
||||||
|
private bool isPromptCompletionEnabled = true; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private IReadOnlyList<string> availableTagCompletionCsvs = Array.Empty<string>(); |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string? selectedTagCompletionCsv; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isCompletionRemoveUnderscoresEnabled = true; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[CustomValidation(typeof(MainSettingsViewModel), nameof(ValidateOutputImageFileNameFormat))] |
||||||
|
private string? outputImageFileNameFormat; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string? outputImageFileNameFormatSample; |
||||||
|
|
||||||
|
public IEnumerable<FileNameFormatVar> 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; |
||||||
|
|
||||||
|
// Debug section |
||||||
|
[ObservableProperty] |
||||||
|
private string? debugPaths; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string? debugCompatInfo; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string? debugGpuInfo; |
||||||
|
|
||||||
|
// Info section |
||||||
|
private const int VersionTapCountThreshold = 7; |
||||||
|
|
||||||
|
[ObservableProperty, NotifyPropertyChangedFor(nameof(VersionFlyoutText))] |
||||||
|
private int versionTapCount; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isVersionTapTeachingTipOpen; |
||||||
|
public string VersionFlyoutText => |
||||||
|
$"You are {VersionTapCountThreshold - VersionTapCount} clicks away from enabling Debug options."; |
||||||
|
|
||||||
|
public string DataDirectory => |
||||||
|
settingsManager.IsLibraryDirSet ? settingsManager.LibraryDir : "Not set"; |
||||||
|
|
||||||
|
public MainSettingsViewModel( |
||||||
|
INotificationService notificationService, |
||||||
|
ISettingsManager settingsManager, |
||||||
|
IPrerequisiteHelper prerequisiteHelper, |
||||||
|
IPyRunner pyRunner, |
||||||
|
ServiceManager<ViewModelBase> dialogFactory, |
||||||
|
ITrackedDownloadService trackedDownloadService, |
||||||
|
SharedState sharedState, |
||||||
|
ICompletionProvider completionProvider, |
||||||
|
IModelIndexService modelIndexService, |
||||||
|
INavigationService<SettingsViewModel> settingsNavigationService |
||||||
|
) |
||||||
|
{ |
||||||
|
this.notificationService = notificationService; |
||||||
|
this.settingsManager = settingsManager; |
||||||
|
this.prerequisiteHelper = prerequisiteHelper; |
||||||
|
this.pyRunner = pyRunner; |
||||||
|
this.dialogFactory = dialogFactory; |
||||||
|
this.trackedDownloadService = trackedDownloadService; |
||||||
|
this.completionProvider = completionProvider; |
||||||
|
this.modelIndexService = modelIndexService; |
||||||
|
this.settingsNavigationService = settingsNavigationService; |
||||||
|
|
||||||
|
SharedState = sharedState; |
||||||
|
|
||||||
|
SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1]; |
||||||
|
SelectedLanguage = Cultures.GetSupportedCultureOrDefault(settingsManager.Settings.Language); |
||||||
|
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown; |
||||||
|
SelectedAnimationScale = settingsManager.Settings.AnimationScale; |
||||||
|
|
||||||
|
settingsManager.RelayPropertyFor(this, vm => vm.SelectedTheme, settings => settings.Theme); |
||||||
|
|
||||||
|
settingsManager.RelayPropertyFor( |
||||||
|
this, |
||||||
|
vm => vm.IsDiscordRichPresenceEnabled, |
||||||
|
settings => settings.IsDiscordRichPresenceEnabled, |
||||||
|
true |
||||||
|
); |
||||||
|
|
||||||
|
settingsManager.RelayPropertyFor( |
||||||
|
this, |
||||||
|
vm => vm.SelectedAnimationScale, |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc /> |
||||||
|
public override async Task OnLoadedAsync() |
||||||
|
{ |
||||||
|
await base.OnLoadedAsync(); |
||||||
|
|
||||||
|
await notificationService.TryAsync(completionProvider.Setup()); |
||||||
|
|
||||||
|
UpdateAvailableTagCompletionCsvs(); |
||||||
|
} |
||||||
|
|
||||||
|
public static ValidationResult ValidateOutputImageFileNameFormat( |
||||||
|
string? format, |
||||||
|
ValidationContext context |
||||||
|
) |
||||||
|
{ |
||||||
|
return FileNameFormatProvider.GetSample().Validate(format ?? string.Empty); |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnSelectedThemeChanged(string? value) |
||||||
|
{ |
||||||
|
// In case design / tests |
||||||
|
if (Application.Current is null) |
||||||
|
return; |
||||||
|
// Change theme |
||||||
|
Application.Current.RequestedThemeVariant = value switch |
||||||
|
{ |
||||||
|
"Dark" => ThemeVariant.Dark, |
||||||
|
"Light" => ThemeVariant.Light, |
||||||
|
_ => ThemeVariant.Default |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnSelectedLanguageChanged(CultureInfo? oldValue, CultureInfo newValue) |
||||||
|
{ |
||||||
|
if (oldValue is null || newValue.Name == Cultures.Current?.Name) |
||||||
|
return; |
||||||
|
|
||||||
|
// Set locale |
||||||
|
if (AvailableLanguages.Contains(newValue)) |
||||||
|
{ |
||||||
|
Logger.Info("Changing language from {Old} to {New}", oldValue, newValue); |
||||||
|
|
||||||
|
Cultures.TrySetSupportedCulture(newValue); |
||||||
|
settingsManager.Transaction(s => s.Language = newValue.Name); |
||||||
|
|
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
Title = Resources.Label_RelaunchRequired, |
||||||
|
Content = Resources.Text_RelaunchRequiredToApplyLanguage, |
||||||
|
DefaultButton = ContentDialogButton.Primary, |
||||||
|
PrimaryButtonText = Resources.Action_Relaunch, |
||||||
|
CloseButtonText = Resources.Action_RelaunchLater |
||||||
|
}; |
||||||
|
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () => |
||||||
|
{ |
||||||
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary) |
||||||
|
{ |
||||||
|
Process.Start(Compat.AppCurrentPath); |
||||||
|
App.Shutdown(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
Logger.Info( |
||||||
|
"Requested invalid language change from {Old} to {New}", |
||||||
|
oldValue, |
||||||
|
newValue |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
partial void OnRemoveSymlinksOnShutdownChanged(bool value) |
||||||
|
{ |
||||||
|
settingsManager.Transaction(s => s.RemoveFolderLinksOnShutdown = value); |
||||||
|
} |
||||||
|
|
||||||
|
public async Task ResetCheckpointCache() |
||||||
|
{ |
||||||
|
settingsManager.Transaction(s => s.InstalledModelHashes = new HashSet<string>()); |
||||||
|
await Task.Run(() => settingsManager.IndexCheckpoints()); |
||||||
|
notificationService.Show( |
||||||
|
"Checkpoint cache reset", |
||||||
|
"The checkpoint cache has been reset.", |
||||||
|
NotificationType.Success |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void NavigateToInferenceSettings() |
||||||
|
{ |
||||||
|
Dispatcher.UIThread.Post( |
||||||
|
() => |
||||||
|
settingsNavigationService.NavigateTo<InferenceSettingsViewModel>( |
||||||
|
new BetterSlideNavigationTransition |
||||||
|
{ |
||||||
|
Effect = SlideNavigationTransitionEffect.FromRight |
||||||
|
} |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#region Package Environment |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task OpenEnvVarsDialog() |
||||||
|
{ |
||||||
|
var viewModel = dialogFactory.Get<EnvVarsViewModel>(); |
||||||
|
|
||||||
|
// Load current settings |
||||||
|
var current = |
||||||
|
settingsManager.Settings.EnvironmentVariables ?? new Dictionary<string, string>(); |
||||||
|
viewModel.EnvVars = new ObservableCollection<EnvVarKeyPair>( |
||||||
|
current.Select(kvp => new EnvVarKeyPair(kvp.Key, kvp.Value)) |
||||||
|
); |
||||||
|
|
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
Content = new EnvVarsDialog { DataContext = viewModel }, |
||||||
|
PrimaryButtonText = Resources.Action_Save, |
||||||
|
IsPrimaryButtonEnabled = true, |
||||||
|
CloseButtonText = Resources.Action_Cancel, |
||||||
|
}; |
||||||
|
|
||||||
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary) |
||||||
|
{ |
||||||
|
// Save settings |
||||||
|
var newEnvVars = viewModel.EnvVars |
||||||
|
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key)) |
||||||
|
.GroupBy(kvp => kvp.Key, StringComparer.Ordinal) |
||||||
|
.ToDictionary(g => g.Key, g => g.First().Value, StringComparer.Ordinal); |
||||||
|
settingsManager.Transaction(s => s.EnvironmentVariables = newEnvVars); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task CheckPythonVersion() |
||||||
|
{ |
||||||
|
var isInstalled = prerequisiteHelper.IsPythonInstalled; |
||||||
|
Logger.Debug($"Check python installed: {isInstalled}"); |
||||||
|
// Ensure python installed |
||||||
|
if (!prerequisiteHelper.IsPythonInstalled) |
||||||
|
{ |
||||||
|
// Need 7z as well for site packages repack |
||||||
|
Logger.Debug("Python not installed, unpacking resources..."); |
||||||
|
await prerequisiteHelper.UnpackResourcesIfNecessary(); |
||||||
|
Logger.Debug("Unpacked resources, installing python..."); |
||||||
|
await prerequisiteHelper.InstallPythonIfNecessary(); |
||||||
|
} |
||||||
|
|
||||||
|
// Get python version |
||||||
|
await pyRunner.Initialize(); |
||||||
|
var result = await pyRunner.GetVersionInfo(); |
||||||
|
// Show dialog box |
||||||
|
var dialog = new ContentDialog |
||||||
|
{ |
||||||
|
Title = Resources.Label_PythonVersionInfo, |
||||||
|
Content = result, |
||||||
|
PrimaryButtonText = Resources.Action_OK, |
||||||
|
IsPrimaryButtonEnabled = true |
||||||
|
}; |
||||||
|
await dialog.ShowAsync(); |
||||||
|
} |
||||||
|
|
||||||
|
#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<FilePickerFileType> |
||||||
|
{ |
||||||
|
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 |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Adds Stability Matrix to Start Menu for the current user. |
||||||
|
/// </summary> |
||||||
|
[RelayCommand] |
||||||
|
private async Task AddToStartMenu() |
||||||
|
{ |
||||||
|
if (!Compat.IsWindows) |
||||||
|
{ |
||||||
|
notificationService.Show("Not supported", "This feature is only supported on Windows."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
await using var _ = new MinimumDelay(200, 300); |
||||||
|
|
||||||
|
var shortcutDir = new DirectoryPath( |
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), |
||||||
|
"Programs" |
||||||
|
); |
||||||
|
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); |
||||||
|
|
||||||
|
var appPath = Compat.AppCurrentPath; |
||||||
|
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); |
||||||
|
await Assets.AppIcon.ExtractTo(iconPath); |
||||||
|
|
||||||
|
WindowsShortcuts.CreateShortcut(shortcutLink, appPath, iconPath, "Stability Matrix"); |
||||||
|
|
||||||
|
notificationService.Show( |
||||||
|
"Added to Start Menu", |
||||||
|
"Stability Matrix has been added to the Start Menu.", |
||||||
|
NotificationType.Success |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Add Stability Matrix to Start Menu for all users. |
||||||
|
/// <remarks>Requires Admin elevation.</remarks> |
||||||
|
/// </summary> |
||||||
|
[RelayCommand] |
||||||
|
private async Task AddToGlobalStartMenu() |
||||||
|
{ |
||||||
|
if (!Compat.IsWindows) |
||||||
|
{ |
||||||
|
notificationService.Show("Not supported", "This feature is only supported on Windows."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Confirmation dialog |
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
Title = |
||||||
|
"This will create a shortcut for Stability Matrix in the Start Menu for all users", |
||||||
|
Content = "You will be prompted for administrator privileges. Continue?", |
||||||
|
PrimaryButtonText = Resources.Action_Yes, |
||||||
|
CloseButtonText = Resources.Action_Cancel, |
||||||
|
DefaultButton = ContentDialogButton.Primary |
||||||
|
}; |
||||||
|
|
||||||
|
if (await dialog.ShowAsync() != ContentDialogResult.Primary) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
await using var _ = new MinimumDelay(200, 300); |
||||||
|
|
||||||
|
var shortcutDir = new DirectoryPath( |
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), |
||||||
|
"Programs" |
||||||
|
); |
||||||
|
var shortcutLink = shortcutDir.JoinFile("Stability Matrix.lnk"); |
||||||
|
|
||||||
|
var appPath = Compat.AppCurrentPath; |
||||||
|
var iconPath = shortcutDir.JoinFile("Stability Matrix.ico"); |
||||||
|
|
||||||
|
// We can't directly write to the targets, so extract to temporary directory first |
||||||
|
using var tempDir = new TempDirectoryPath(); |
||||||
|
|
||||||
|
await Assets.AppIcon.ExtractTo(tempDir.JoinFile("Stability Matrix.ico")); |
||||||
|
WindowsShortcuts.CreateShortcut( |
||||||
|
tempDir.JoinFile("Stability Matrix.lnk"), |
||||||
|
appPath, |
||||||
|
iconPath, |
||||||
|
"Stability Matrix" |
||||||
|
); |
||||||
|
|
||||||
|
// Move to target |
||||||
|
try |
||||||
|
{ |
||||||
|
var moveLinkResult = await WindowsElevated.MoveFiles( |
||||||
|
(tempDir.JoinFile("Stability Matrix.lnk"), shortcutLink), |
||||||
|
(tempDir.JoinFile("Stability Matrix.ico"), iconPath) |
||||||
|
); |
||||||
|
if (moveLinkResult != 0) |
||||||
|
{ |
||||||
|
notificationService.ShowPersistent( |
||||||
|
"Failed to create shortcut", |
||||||
|
$"Could not copy shortcut", |
||||||
|
NotificationType.Error |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
catch (Win32Exception e) |
||||||
|
{ |
||||||
|
// We'll get this exception if user cancels UAC |
||||||
|
Logger.Warn(e, "Could not create shortcut"); |
||||||
|
notificationService.Show("Could not create shortcut", "", NotificationType.Warning); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
notificationService.Show( |
||||||
|
"Added to Start Menu", |
||||||
|
"Stability Matrix has been added to the Start Menu for all users.", |
||||||
|
NotificationType.Success |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public async Task PickNewDataDirectory() |
||||||
|
{ |
||||||
|
var viewModel = dialogFactory.Get<SelectDataDirectoryViewModel>(); |
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
IsPrimaryButtonEnabled = false, |
||||||
|
IsSecondaryButtonEnabled = false, |
||||||
|
IsFooterVisible = false, |
||||||
|
Content = new SelectDataDirectoryDialog { DataContext = viewModel } |
||||||
|
}; |
||||||
|
|
||||||
|
var result = await dialog.ShowAsync(); |
||||||
|
if (result == ContentDialogResult.Primary) |
||||||
|
{ |
||||||
|
// 1. For portable mode, call settings.SetPortableMode() |
||||||
|
if (viewModel.IsPortableMode) |
||||||
|
{ |
||||||
|
settingsManager.SetPortableMode(); |
||||||
|
} |
||||||
|
// 2. For custom path, call settings.SetLibraryPath(path) |
||||||
|
else |
||||||
|
{ |
||||||
|
settingsManager.SetLibraryPath(viewModel.DataDirectory); |
||||||
|
} |
||||||
|
|
||||||
|
// Restart |
||||||
|
var restartDialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
Title = "Restart required", |
||||||
|
Content = "Stability Matrix must be restarted for the changes to take effect.", |
||||||
|
PrimaryButtonText = Resources.Action_Restart, |
||||||
|
DefaultButton = ContentDialogButton.Primary, |
||||||
|
IsSecondaryButtonEnabled = false, |
||||||
|
}; |
||||||
|
await restartDialog.ShowAsync(); |
||||||
|
|
||||||
|
Process.Start(Compat.AppCurrentPath); |
||||||
|
App.Shutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endregion |
||||||
|
|
||||||
|
#region Debug Section |
||||||
|
public void LoadDebugInfo() |
||||||
|
{ |
||||||
|
var assembly = Assembly.GetExecutingAssembly(); |
||||||
|
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); |
||||||
|
DebugPaths = $"""
|
||||||
|
Current Working Directory [Environment.CurrentDirectory] |
||||||
|
"{Environment.CurrentDirectory}" |
||||||
|
App Directory [Assembly.GetExecutingAssembly().Location] |
||||||
|
"{assembly.Location}" |
||||||
|
App Directory [AppContext.BaseDirectory] |
||||||
|
"{AppContext.BaseDirectory}" |
||||||
|
AppData Directory [SpecialFolder.ApplicationData] |
||||||
|
"{appData}" |
||||||
|
""";
|
||||||
|
|
||||||
|
// 1. Check portable mode |
||||||
|
var appDir = Compat.AppCurrentDir; |
||||||
|
var expectedPortableFile = Path.Combine(appDir, "Data", ".sm-portable"); |
||||||
|
var isPortableMode = File.Exists(expectedPortableFile); |
||||||
|
|
||||||
|
DebugCompatInfo = $"""
|
||||||
|
Platform: {Compat.Platform} |
||||||
|
AppData: {Compat.AppData} |
||||||
|
AppDataHome: {Compat.AppDataHome} |
||||||
|
AppCurrentDir: {Compat.AppCurrentDir} |
||||||
|
ExecutableName: {Compat.GetExecutableName()} |
||||||
|
-- Settings -- |
||||||
|
Expected Portable Marker file: {expectedPortableFile} |
||||||
|
Portable Marker file exists: {isPortableMode} |
||||||
|
IsLibraryDirSet = {settingsManager.IsLibraryDirSet} |
||||||
|
IsPortableMode = {settingsManager.IsPortableMode} |
||||||
|
""";
|
||||||
|
|
||||||
|
// Get Gpu info |
||||||
|
var gpuInfo = ""; |
||||||
|
foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate()) |
||||||
|
{ |
||||||
|
gpuInfo += $"[{i + 1}] {gpu}\n"; |
||||||
|
} |
||||||
|
DebugGpuInfo = gpuInfo; |
||||||
|
} |
||||||
|
|
||||||
|
// Debug buttons |
||||||
|
[RelayCommand] |
||||||
|
private void DebugNotification() |
||||||
|
{ |
||||||
|
notificationService.Show( |
||||||
|
new Notification( |
||||||
|
title: "Test Notification", |
||||||
|
message: "Here is some message", |
||||||
|
type: NotificationType.Information |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugContentDialog() |
||||||
|
{ |
||||||
|
var dialog = new ContentDialog |
||||||
|
{ |
||||||
|
DefaultButton = ContentDialogButton.Primary, |
||||||
|
Title = "Test title", |
||||||
|
PrimaryButtonText = Resources.Action_OK, |
||||||
|
CloseButtonText = Resources.Action_Close |
||||||
|
}; |
||||||
|
|
||||||
|
var result = await dialog.ShowAsync(); |
||||||
|
notificationService.Show(new Notification("Content dialog closed", $"Result: {result}")); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private void DebugThrowException() |
||||||
|
{ |
||||||
|
throw new OperationCanceledException("Example Message"); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand(FlowExceptionsToTaskScheduler = true)] |
||||||
|
private async Task DebugThrowAsyncException() |
||||||
|
{ |
||||||
|
await Task.Yield(); |
||||||
|
|
||||||
|
throw new ApplicationException("Example Message"); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugMakeImageGrid() |
||||||
|
{ |
||||||
|
var provider = App.StorageProvider; |
||||||
|
var files = await provider.OpenFilePickerAsync( |
||||||
|
new FilePickerOpenOptions() { AllowMultiple = true } |
||||||
|
); |
||||||
|
|
||||||
|
if (files.Count == 0) |
||||||
|
return; |
||||||
|
|
||||||
|
var images = await files.SelectAsync( |
||||||
|
async f => SKImage.FromEncodedData(await f.OpenReadAsync()) |
||||||
|
); |
||||||
|
|
||||||
|
var grid = ImageProcessor.CreateImageGrid(images.ToImmutableArray()); |
||||||
|
|
||||||
|
// Show preview |
||||||
|
|
||||||
|
using var peekPixels = grid.PeekPixels(); |
||||||
|
using var data = peekPixels.Encode(SKEncodedImageFormat.Jpeg, 100); |
||||||
|
await using var stream = data.AsStream(); |
||||||
|
|
||||||
|
var bitmap = WriteableBitmap.Decode(stream); |
||||||
|
|
||||||
|
var galleryImages = new List<ImageSource> { new(bitmap), }; |
||||||
|
galleryImages.AddRange(files.Select(f => new ImageSource(f.Path.ToString()))); |
||||||
|
|
||||||
|
var imageBox = new ImageGalleryCard |
||||||
|
{ |
||||||
|
Width = 1000, |
||||||
|
Height = 900, |
||||||
|
DataContext = dialogFactory.Get<ImageGalleryCardViewModel>(vm => |
||||||
|
{ |
||||||
|
vm.ImageSources.AddRange(galleryImages); |
||||||
|
}) |
||||||
|
}; |
||||||
|
|
||||||
|
var dialog = new BetterContentDialog |
||||||
|
{ |
||||||
|
MaxDialogWidth = 1000, |
||||||
|
MaxDialogHeight = 1000, |
||||||
|
FullSizeDesired = true, |
||||||
|
Content = imageBox, |
||||||
|
CloseButtonText = "Close", |
||||||
|
ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled, |
||||||
|
}; |
||||||
|
|
||||||
|
await dialog.ShowAsync(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugLoadCompletionCsv() |
||||||
|
{ |
||||||
|
var provider = App.StorageProvider; |
||||||
|
var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions()); |
||||||
|
|
||||||
|
if (files.Count == 0) |
||||||
|
return; |
||||||
|
|
||||||
|
await completionProvider.LoadFromFile(files[0].TryGetLocalPath()!, true); |
||||||
|
|
||||||
|
notificationService.Show("Loaded completion file", ""); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugImageMetadata() |
||||||
|
{ |
||||||
|
var provider = App.StorageProvider; |
||||||
|
var files = await provider.OpenFilePickerAsync(new FilePickerOpenOptions()); |
||||||
|
|
||||||
|
if (files.Count == 0) |
||||||
|
return; |
||||||
|
|
||||||
|
var metadata = ImageMetadata.ParseFile(files[0].TryGetLocalPath()!); |
||||||
|
var textualTags = metadata.GetTextualData()?.ToArray(); |
||||||
|
|
||||||
|
if (textualTags is null) |
||||||
|
{ |
||||||
|
notificationService.Show("No textual data found", ""); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (metadata.GetGenerationParameters() is { } parameters) |
||||||
|
{ |
||||||
|
var parametersJson = JsonSerializer.Serialize(parameters); |
||||||
|
var dialog = DialogHelper.CreateJsonDialog(parametersJson, "Generation Parameters"); |
||||||
|
await dialog.ShowAsync(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugRefreshModelsIndex() |
||||||
|
{ |
||||||
|
await modelIndexService.RefreshIndex(); |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task DebugTrackedDownload() |
||||||
|
{ |
||||||
|
var textFields = new TextBoxField[] |
||||||
|
{ |
||||||
|
new() { Label = "Url", }, |
||||||
|
new() { Label = "File path" } |
||||||
|
}; |
||||||
|
|
||||||
|
var dialog = DialogHelper.CreateTextEntryDialog("Add download", "", textFields); |
||||||
|
|
||||||
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary) |
||||||
|
{ |
||||||
|
var url = textFields[0].Text; |
||||||
|
var filePath = textFields[1].Text; |
||||||
|
var download = trackedDownloadService.NewDownload(new Uri(url), new FilePath(filePath)); |
||||||
|
download.Start(); |
||||||
|
} |
||||||
|
} |
||||||
|
#endregion |
||||||
|
|
||||||
|
#region Info Section |
||||||
|
|
||||||
|
public void OnVersionClick() |
||||||
|
{ |
||||||
|
// Ignore if already enabled |
||||||
|
if (SharedState.IsDebugMode) |
||||||
|
return; |
||||||
|
|
||||||
|
VersionTapCount++; |
||||||
|
|
||||||
|
switch (VersionTapCount) |
||||||
|
{ |
||||||
|
// Reached required threshold |
||||||
|
case >= VersionTapCountThreshold: |
||||||
|
{ |
||||||
|
IsVersionTapTeachingTipOpen = false; |
||||||
|
// Enable debug options |
||||||
|
SharedState.IsDebugMode = true; |
||||||
|
notificationService.Show( |
||||||
|
"Debug options enabled", |
||||||
|
"Warning: Improper use may corrupt application state or cause loss of data." |
||||||
|
); |
||||||
|
VersionTapCount = 0; |
||||||
|
break; |
||||||
|
} |
||||||
|
// Open teaching tip above 3rd click |
||||||
|
case >= 3: |
||||||
|
IsVersionTapTeachingTipOpen = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async Task ShowLicensesDialog() |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
var markdown = GetLicensesMarkdown(); |
||||||
|
|
||||||
|
var dialog = DialogHelper.CreateMarkdownDialog(markdown, "Licenses"); |
||||||
|
dialog.MaxDialogHeight = 600; |
||||||
|
await dialog.ShowAsync(); |
||||||
|
} |
||||||
|
catch (Exception e) |
||||||
|
{ |
||||||
|
notificationService.Show( |
||||||
|
"Failed to read licenses information", |
||||||
|
$"{e}", |
||||||
|
NotificationType.Error |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static string GetLicensesMarkdown() |
||||||
|
{ |
||||||
|
// Read licenses.json |
||||||
|
using var reader = new StreamReader(Assets.LicensesJson.Open()); |
||||||
|
var licenses = |
||||||
|
JsonSerializer.Deserialize<IReadOnlyList<LicenseInfo>>(reader.ReadToEnd()) |
||||||
|
?? throw new InvalidOperationException("Failed to read licenses.json"); |
||||||
|
|
||||||
|
// Generate markdown |
||||||
|
var builder = new StringBuilder(); |
||||||
|
foreach (var license in licenses) |
||||||
|
{ |
||||||
|
builder.AppendLine( |
||||||
|
$"## [{license.PackageName}]({license.PackageUrl}) by {string.Join(", ", license.Authors)}" |
||||||
|
); |
||||||
|
builder.AppendLine(); |
||||||
|
builder.AppendLine(license.Description); |
||||||
|
builder.AppendLine(); |
||||||
|
builder.AppendLine($"[{license.LicenseUrl}]({license.LicenseUrl})"); |
||||||
|
builder.AppendLine(); |
||||||
|
} |
||||||
|
|
||||||
|
return builder.ToString(); |
||||||
|
} |
||||||
|
|
||||||
|
#endregion |
||||||
|
} |
@ -1,16 +1,69 @@ |
|||||||
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
<controls:UserControlBase |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.Settings.InferenceSettingsPage" |
||||||
|
xmlns="https://github.com/avaloniaui" |
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
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:d="http://schemas.microsoft.com/expression/blend/2008" |
||||||
|
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||||
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls" |
|
||||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
|
||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
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" |
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings" |
||||||
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}" |
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}" |
||||||
|
d:DesignHeight="450" |
||||||
|
d:DesignWidth="800" |
||||||
x:DataType="vmSettings:InferenceSettingsViewModel" |
x:DataType="vmSettings:InferenceSettingsViewModel" |
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
mc:Ignorable="d"> |
||||||
x:Class="StabilityMatrix.Avalonia.Views.Settings.InferenceSettingsPage"> |
<ScrollViewer VerticalScrollBarVisibility="Auto"> |
||||||
Welcome to Avalonia! |
<StackPanel Margin="8,16" Spacing="8"> |
||||||
|
<!-- Theme --> |
||||||
|
<Grid RowDefinitions="Auto,*,*,*"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="Stuff" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_Theme}" |
||||||
|
IconSource="WeatherMoon"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ComboBox MinWidth="100" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_Language}" |
||||||
|
IconSource="Character"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ComboBox MinWidth="100" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="3" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="Inference" |
||||||
|
IconSource="Code" /> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Checkpoints Manager Options --> |
||||||
|
<Grid RowDefinitions="auto,*,Auto"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="Other stuff" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0" |
||||||
|
IconSource="Folder"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<CheckBox Margin="8" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
</StackPanel> |
||||||
|
</ScrollViewer> |
||||||
</controls:UserControlBase> |
</controls:UserControlBase> |
||||||
|
@ -1,19 +1,13 @@ |
|||||||
using Avalonia.Controls; |
using StabilityMatrix.Avalonia.Controls; |
||||||
using Avalonia.Markup.Xaml; |
|
||||||
using StabilityMatrix.Core.Attributes; |
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
namespace StabilityMatrix.Avalonia.Views.Settings; |
namespace StabilityMatrix.Avalonia.Views.Settings; |
||||||
|
|
||||||
[Singleton] |
[Singleton] |
||||||
public partial class InferenceSettingsPage : UserControl |
public partial class InferenceSettingsPage : UserControlBase |
||||||
{ |
{ |
||||||
public InferenceSettingsPage() |
public InferenceSettingsPage() |
||||||
{ |
{ |
||||||
InitializeComponent(); |
InitializeComponent(); |
||||||
} |
} |
||||||
|
|
||||||
private void InitializeComponent() |
|
||||||
{ |
|
||||||
AvaloniaXamlLoader.Load(this); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,533 @@ |
|||||||
|
<controls:UserControlBase |
||||||
|
x:Class="StabilityMatrix.Avalonia.Views.Settings.MainSettingsPage" |
||||||
|
xmlns="https://github.com/avaloniaui" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
|
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" |
||||||
|
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||||
|
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" |
||||||
|
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: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" |
||||||
|
Focusable="True" |
||||||
|
d:DataContext="{x:Static mocks:DesignData.MainSettingsViewModel}" |
||||||
|
d:DesignHeight="700" |
||||||
|
d:DesignWidth="800" |
||||||
|
x:CompileBindings="True" |
||||||
|
x:DataType="vmSettings:MainSettingsViewModel" |
||||||
|
mc:Ignorable="d"> |
||||||
|
|
||||||
|
<controls:UserControlBase.Resources> |
||||||
|
<converters:CultureInfoDisplayConverter x:Key="CultureInfoDisplayConverter" /> |
||||||
|
</controls:UserControlBase.Resources> |
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto"> |
||||||
|
<StackPanel Margin="8,16" Spacing="8"> |
||||||
|
<!-- Theme --> |
||||||
|
<Grid RowDefinitions="Auto,*,*,*"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_Appearance}" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_Theme}" |
||||||
|
IconSource="WeatherMoon"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ComboBox |
||||||
|
MinWidth="100" |
||||||
|
ItemsSource="{Binding AvailableThemes}" |
||||||
|
SelectedItem="{Binding SelectedTheme}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_Language}" |
||||||
|
IconSource="Character"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ComboBox |
||||||
|
MinWidth="100" |
||||||
|
DisplayMemberBinding="{Binding Converter={StaticResource CultureInfoDisplayConverter}}" |
||||||
|
ItemsSource="{Binding AvailableLanguages}" |
||||||
|
SelectedItem="{Binding SelectedLanguage}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="3" |
||||||
|
IsVisible="{Binding SharedState.IsDebugMode}" |
||||||
|
Margin="8,0,8,4" |
||||||
|
IsClickEnabled="True" |
||||||
|
Command="{Binding NavigateToInferenceSettingsCommand}" |
||||||
|
Header="Inference (Test)" |
||||||
|
IconSource="Code" |
||||||
|
ActionIconSource="ChevronRight"> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Checkpoints Manager Options --> |
||||||
|
<Grid RowDefinitions="auto,*,Auto"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_CheckpointManager}" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0" |
||||||
|
Description="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown_Details}" |
||||||
|
Header="{x:Static lang:Resources.Label_RemoveSymlinksOnShutdown}" |
||||||
|
IconSource="Folder"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<CheckBox Margin="8" IsChecked="{Binding RemoveSymlinksOnShutdown}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,4" |
||||||
|
Description="{x:Static lang:Resources.Label_ResetCheckpointsCache_Details}" |
||||||
|
Header="{x:Static lang:Resources.Label_ResetCheckpointsCache}" |
||||||
|
IconSource="Refresh"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<Button Command="{Binding ResetCheckpointCache}" Content="{x:Static lang:Resources.Label_ResetCheckpointsCache}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Inference UI --> |
||||||
|
<Grid Margin="0,8,0,0" RowDefinitions="auto,*,*,*"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="Inference" /> |
||||||
|
<!-- Auto Completion --> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="Prompt Auto Completion"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<controls:FASymbolIconSource Symbol="fa-solid fa-wand-magic-sparkles" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
|
||||||
|
<!-- Enable toggle --> |
||||||
|
<ui:SettingsExpanderItem Content="Enable"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<ToggleSwitch IsChecked="{Binding IsPromptCompletionEnabled}" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<!-- Tag csv selection --> |
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Content="Tag Source" |
||||||
|
Description="Tags to use for completion in .csv format (Compatible with a1111-sd-webui-tagcomplete)" |
||||||
|
IconSource="Tag" |
||||||
|
IsEnabled="{Binding IsPromptCompletionEnabled}"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<ui:FAComboBox ItemsSource="{Binding AvailableTagCompletionCsvs}" SelectedItem="{Binding SelectedTagCompletionCsv}" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<!-- Tag csv import --> |
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Content="Import Tag Source .csv" |
||||||
|
IconSource="Add" |
||||||
|
IsEnabled="{Binding IsPromptCompletionEnabled}"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button Command="{Binding ImportTagCsvCommand}" Content="Import" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<!-- Remove underscores --> |
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Content="Replace underscores with spaces when inserting completions" |
||||||
|
IconSource="Underline" |
||||||
|
IsEnabled="{Binding IsPromptCompletionEnabled}"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<CheckBox Margin="8" IsChecked="{Binding IsCompletionRemoveUnderscoresEnabled}" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
<!-- Image Viewer --> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="Image Viewer" |
||||||
|
IconSource="Image"> |
||||||
|
|
||||||
|
<!-- Pixel grid --> |
||||||
|
<ui:SettingsExpanderItem Content="Show pixel grid at high zoom levels" IconSource="ViewAll"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<CheckBox Margin="8" IsChecked="{Binding IsImageViewerPixelGridEnabled}" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
<!-- Output Image Files --> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="3" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="Output Image Files"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<fluentIcons:SymbolIconSource Symbol="TabDesktopImage" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
<!-- File name pattern --> |
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Content="File name pattern" |
||||||
|
Description="{Binding OutputImageFileNameFormatSample}" |
||||||
|
IconSource="Rename"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<TextBox |
||||||
|
Name="OutputImageFileNameFormatTextBox" |
||||||
|
MinWidth="150" |
||||||
|
FontFamily="Cascadia Code,Consolas,Menlo,Monospace" |
||||||
|
FontSize="13" |
||||||
|
Text="{Binding OutputImageFileNameFormat}" |
||||||
|
Watermark="{x:Static inference:FileNameFormat.DefaultTemplate}" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
<ui:TeachingTip |
||||||
|
Title="Format Variables" |
||||||
|
Grid.Row="3" |
||||||
|
IsOpen="{Binding #OutputImageFileNameFormatTextBox.IsFocused}" |
||||||
|
PreferredPlacement="Top" |
||||||
|
Target="{Binding #OutputImageFileNameFormatTextBox, Mode=OneWay}"> |
||||||
|
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding OutputImageFileNameFormatVars}" /> |
||||||
|
<!--<mdxaml:MarkdownScrollViewer |
||||||
|
Markdown="{Binding OutputImageFileNameFormatGuideMarkdown}"/>--> |
||||||
|
</ui:TeachingTip> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Environment Options --> |
||||||
|
<Grid RowDefinitions="Auto, Auto, Auto"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_PackageEnvironment}" /> |
||||||
|
|
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0" |
||||||
|
Header="{x:Static lang:Resources.Label_EnvironmentVariables}" |
||||||
|
IconSource="OtherUser"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<Button Command="{Binding OpenEnvVarsDialogCommand}" Content="{x:Static lang:Resources.Action_Edit}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
|
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_EmbeddedPython}"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<controls:FASymbolIconSource Symbol="fa-brands fa-python" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="16"> |
||||||
|
<controls:ProgressRing |
||||||
|
BorderThickness="3" |
||||||
|
IsEnabled="{Binding CheckPythonVersionCommand.IsRunning}" |
||||||
|
IsIndeterminate="True" |
||||||
|
IsVisible="{Binding CheckPythonVersionCommand.IsRunning}" /> |
||||||
|
<Button Command="{Binding CheckPythonVersionCommand}" Content="{x:Static lang:Resources.Action_CheckVersion}" /> |
||||||
|
</StackPanel> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Integrations --> |
||||||
|
<Grid RowDefinitions="auto,*"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_Integrations}" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Header="{x:Static lang:Resources.Label_DiscordRichPresence}"> |
||||||
|
<ui:SettingsExpander.IconSource> |
||||||
|
<controls:FASymbolIconSource Symbol="fa-brands fa-discord" /> |
||||||
|
</ui:SettingsExpander.IconSource> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<ToggleSwitch IsChecked="{Binding IsDiscordRichPresenceEnabled}" /> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- System Options --> |
||||||
|
<Grid RowDefinitions="auto, auto, auto"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_System}" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,4" |
||||||
|
Description="{x:Static lang:Resources.Label_AddToStartMenu_Details}" |
||||||
|
Header="{x:Static lang:Resources.Label_AddToStartMenu}" |
||||||
|
IconSource="StarAdd" |
||||||
|
ToolTip.Tip="{OnPlatform Default={x:Static lang:Resources.Label_OnlyAvailableOnWindows}, |
||||||
|
Windows={x:Null}}"> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8"> |
||||||
|
<controls:ProgressRing |
||||||
|
BorderThickness="3" |
||||||
|
IsEnabled="{Binding IsVisible, RelativeSource={RelativeSource Self}}" |
||||||
|
IsIndeterminate="True"> |
||||||
|
<controls:ProgressRing.IsVisible> |
||||||
|
<MultiBinding Converter="{x:Static BoolConverters.Or}"> |
||||||
|
<Binding Path="AddToStartMenuCommand.IsRunning" /> |
||||||
|
<Binding Path="AddToGlobalStartMenuCommand.IsRunning" /> |
||||||
|
</MultiBinding> |
||||||
|
</controls:ProgressRing.IsVisible> |
||||||
|
</controls:ProgressRing> |
||||||
|
|
||||||
|
<SplitButton |
||||||
|
Command="{Binding AddToStartMenuCommand}" |
||||||
|
Content="{x:Static lang:Resources.Action_AddForCurrentUser}" |
||||||
|
IsEnabled="{OnPlatform Default=False, |
||||||
|
Windows=True}"> |
||||||
|
<SplitButton.Flyout> |
||||||
|
<ui:FAMenuFlyout Placement="Bottom"> |
||||||
|
<ui:MenuFlyoutItem |
||||||
|
Command="{Binding AddToGlobalStartMenuCommand}" |
||||||
|
IconSource="Admin" |
||||||
|
Text="{x:Static lang:Resources.Action_AddForAllUsers}" /> |
||||||
|
</ui:FAMenuFlyout> |
||||||
|
</SplitButton.Flyout> |
||||||
|
</SplitButton> |
||||||
|
</StackPanel> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="2" |
||||||
|
Margin="8,0" |
||||||
|
Description="{x:Static lang:Resources.Label_SelectNewDataDirectory_Details}" |
||||||
|
IconSource="MoveToFolder"> |
||||||
|
<ui:SettingsExpander.Header> |
||||||
|
<StackPanel Orientation="Vertical"> |
||||||
|
<TextBlock Text="{x:Static lang:Resources.Label_SelectNewDataDirectory}" /> |
||||||
|
<TextBlock FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}"> |
||||||
|
<Run Text="{x:Static lang:Resources.Label_CurrentDirectory}" /> |
||||||
|
<Run Text="{Binding DataDirectory}" /> |
||||||
|
</TextBlock> |
||||||
|
</StackPanel> |
||||||
|
</ui:SettingsExpander.Header> |
||||||
|
<ui:SettingsExpander.Footer> |
||||||
|
<Button Command="{Binding PickNewDataDirectory}"> |
||||||
|
<Grid ColumnDefinitions="Auto, Auto"> |
||||||
|
<avalonia:Icon |
||||||
|
Grid.Row="0" |
||||||
|
Margin="0,0,8,0" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Value="fa-solid fa-folder-open" /> |
||||||
|
<TextBlock |
||||||
|
Grid.Column="1" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Text="{x:Static lang:Resources.Action_SelectDirectory}" /> |
||||||
|
</Grid> |
||||||
|
</Button> |
||||||
|
</ui:SettingsExpander.Footer> |
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Debug Options --> |
||||||
|
<Grid IsVisible="{Binding SharedState.IsDebugMode}" RowDefinitions="auto,*"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,0,0,8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="Debug Options" /> |
||||||
|
<ui:SettingsExpander |
||||||
|
Grid.Row="1" |
||||||
|
Margin="8,0,8,0" |
||||||
|
Command="{Binding LoadDebugInfo}" |
||||||
|
Header="Debug Options" |
||||||
|
IconSource="Code"> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Description="Paths" |
||||||
|
IconSource="Folder"> |
||||||
|
<SelectableTextBlock |
||||||
|
Foreground="{DynamicResource TextControlPlaceholderForeground}" |
||||||
|
Text="{Binding DebugPaths}" |
||||||
|
TextWrapping="WrapWithOverflow" /> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Description="Compat Info" |
||||||
|
IconSource="StarFilled"> |
||||||
|
<SelectableTextBlock |
||||||
|
Foreground="{DynamicResource TextControlPlaceholderForeground}" |
||||||
|
Text="{Binding DebugCompatInfo}" |
||||||
|
TextWrapping="WrapWithOverflow" /> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Description="GPU Info" |
||||||
|
IconSource="FullScreenMaximize"> |
||||||
|
<SelectableTextBlock |
||||||
|
Foreground="{DynamicResource TextControlPlaceholderForeground}" |
||||||
|
Text="{Binding DebugGpuInfo}" |
||||||
|
TextWrapping="WrapWithOverflow" /> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Content="Animation Scale" |
||||||
|
Description="Lower values = faster animations. 0x means animations are instant." |
||||||
|
IconSource="Clock"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<ComboBox ItemsSource="{Binding AnimationScaleOptions}" SelectedItem="{Binding SelectedAnimationScale}"> |
||||||
|
<ComboBox.ItemTemplate> |
||||||
|
<DataTemplate> |
||||||
|
<TextBlock> |
||||||
|
<Run Text="{Binding}" /><Run Text="x" /> |
||||||
|
</TextBlock> |
||||||
|
</DataTemplate> |
||||||
|
</ComboBox.ItemTemplate> |
||||||
|
</ComboBox> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Content="Notification" |
||||||
|
IconSource="CommentAdd"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button Command="{Binding DebugNotificationCommand}" Content="New Notification" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Content="Content Dialog" |
||||||
|
IconSource="NewWindow"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button Command="{Binding DebugContentDialogCommand}" Content="Show Dialog" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0" |
||||||
|
Content="Exceptions" |
||||||
|
IconSource="Flag"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button Command="{Binding DebugThrowExceptionCommand}" Content="Unhandled Exception" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0,4,4" |
||||||
|
Content="Download Manager tests" |
||||||
|
IconSource="Flag"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button |
||||||
|
Margin="0,8" |
||||||
|
Command="{Binding DebugTrackedDownloadCommand}" |
||||||
|
Content="Add Tracked Download" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0,4,4" |
||||||
|
Content="Refresh Models Index" |
||||||
|
IconSource="SyncFolder"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button |
||||||
|
Margin="0,8" |
||||||
|
Command="{Binding DebugRefreshModelsIndexCommand}" |
||||||
|
Content="Refresh Index" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0,4,4" |
||||||
|
Content="Make image grid" |
||||||
|
IconSource="Image"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button |
||||||
|
Margin="0,8" |
||||||
|
Command="{Binding DebugMakeImageGridCommand}" |
||||||
|
Content="Select images" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
<ui:SettingsExpanderItem |
||||||
|
Margin="4,0,4,4" |
||||||
|
Content="Image metadata parser" |
||||||
|
IconSource="Flag"> |
||||||
|
<ui:SettingsExpanderItem.Footer> |
||||||
|
<Button |
||||||
|
Margin="0,8" |
||||||
|
Command="{Binding DebugImageMetadataCommand}" |
||||||
|
Content="Choose image" /> |
||||||
|
</ui:SettingsExpanderItem.Footer> |
||||||
|
</ui:SettingsExpanderItem> |
||||||
|
|
||||||
|
</ui:SettingsExpander> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- TODO: Directories card --> |
||||||
|
|
||||||
|
<Grid RowDefinitions="auto,*"> |
||||||
|
<StackPanel |
||||||
|
Grid.Row="1" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
Orientation="Vertical"> |
||||||
|
<TextBlock |
||||||
|
Margin="0,8" |
||||||
|
FontSize="15" |
||||||
|
FontWeight="Bold" |
||||||
|
Text="{x:Static lang:Resources.Label_About}" /> |
||||||
|
<Image |
||||||
|
Width="112" |
||||||
|
Height="112" |
||||||
|
Margin="8" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
Source="/Assets/Icon.png" /> |
||||||
|
<TextBlock |
||||||
|
Margin="8" |
||||||
|
FontWeight="Medium" |
||||||
|
Text="{x:Static lang:Resources.Label_StabilityMatrix}" /> |
||||||
|
<Panel> |
||||||
|
<Button |
||||||
|
Name="VersionButton" |
||||||
|
Margin="8,0,8,8" |
||||||
|
Padding="2,0,2,0" |
||||||
|
BorderThickness="0" |
||||||
|
Classes="transparent" |
||||||
|
Command="{Binding OnVersionClick}" |
||||||
|
Content="{Binding AppVersion}" /> |
||||||
|
<ui:TeachingTip |
||||||
|
Title="{Binding VersionFlyoutText}" |
||||||
|
IsOpen="{Binding IsVersionTapTeachingTipOpen}" |
||||||
|
PreferredPlacement="RightTop" |
||||||
|
Target="{Binding #VersionButton}" /> |
||||||
|
</Panel> |
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> |
||||||
|
<Button |
||||||
|
Margin="8" |
||||||
|
HorizontalAlignment="Left" |
||||||
|
Command="{Binding ShowLicensesDialogCommand}" |
||||||
|
Content="{x:Static lang:Resources.Label_LicenseAndOpenSourceNotices}" /> |
||||||
|
</StackPanel> |
||||||
|
</StackPanel> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<!-- Extra space at the bottom --> |
||||||
|
<Panel Margin="0,0,0,16" /> |
||||||
|
</StackPanel> |
||||||
|
</ScrollViewer> |
||||||
|
</controls:UserControlBase> |
@ -0,0 +1,13 @@ |
|||||||
|
using StabilityMatrix.Avalonia.Controls; |
||||||
|
using StabilityMatrix.Core.Attributes; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Avalonia.Views.Settings; |
||||||
|
|
||||||
|
[Singleton] |
||||||
|
public partial class MainSettingsPage : UserControlBase |
||||||
|
{ |
||||||
|
public MainSettingsPage() |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue