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.Core.Attributes; |
||||
using Symbol = FluentIcons.Common.Symbol; |
||||
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Settings; |
||||
|
||||
[View(typeof(InferenceSettingsPage))] |
||||
[Singleton] |
||||
public partial class InferenceSettingsViewModel : ViewModelBase { } |
||||
[Singleton, ManagedService] |
||||
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" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
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:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime" |
||||
xmlns:vmSettings="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Settings" |
||||
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}" |
||||
x:DataType="vmSettings:InferenceSettingsViewModel" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:Class="StabilityMatrix.Avalonia.Views.Settings.InferenceSettingsPage"> |
||||
Welcome to Avalonia! |
||||
<controls:UserControlBase |
||||
x:Class="StabilityMatrix.Avalonia.Views.Settings.InferenceSettingsPage" |
||||
xmlns="https://github.com/avaloniaui" |
||||
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: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" |
||||
d:DataContext="{x:Static mocks:DesignData.InferenceSettingsViewModel}" |
||||
d:DesignHeight="450" |
||||
d:DesignWidth="800" |
||||
x:DataType="vmSettings:InferenceSettingsViewModel" |
||||
mc:Ignorable="d"> |
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"> |
||||
<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> |
||||
|
@ -1,19 +1,13 @@
|
||||
using Avalonia.Controls; |
||||
using Avalonia.Markup.Xaml; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views.Settings; |
||||
|
||||
[Singleton] |
||||
public partial class InferenceSettingsPage : UserControl |
||||
public partial class InferenceSettingsPage : UserControlBase |
||||
{ |
||||
public InferenceSettingsPage() |
||||
{ |
||||
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