Browse Source

Merge branch 'main' into inference

pull/165/head
Ionite 1 year ago committed by GitHub
parent
commit
e6ded40e73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      StabilityMatrix.Avalonia/Animations/BaseTransitionInfo.cs
  2. 59
      StabilityMatrix.Avalonia/Animations/BetterDrillInNavigationTransition.cs
  3. 66
      StabilityMatrix.Avalonia/Animations/BetterEntranceNavigationTransition.cs
  4. 96
      StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs
  5. 37
      StabilityMatrix.Avalonia/Services/NavigationService.cs
  6. 19
      StabilityMatrix.Avalonia/ViewLocator.cs
  7. 3
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs
  8. 55
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  9. 19
      StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
  10. 2
      StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml
  11. 3
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  12. 2
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  13. 112
      StabilityMatrix.Avalonia/Views/SettingsPage.axaml
  14. 8
      StabilityMatrix.Core/Helper/PrerequisiteHelper.cs
  15. 6
      StabilityMatrix.Core/Models/PackageVersion.cs
  16. 16
      StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
  17. 2
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  18. 128
      StabilityMatrix.Core/Models/Packages/InvokeAI.cs
  19. 35
      StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
  20. 2
      StabilityMatrix.Core/Models/Settings/Settings.cs

12
StabilityMatrix.Avalonia/Animations/BaseTransitionInfo.cs

@ -0,0 +1,12 @@
using System;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public abstract class BaseTransitionInfo : NavigationTransitionInfo
{
/// <summary>
/// The duration of the animation at 1x animation scale
/// </summary>
public abstract TimeSpan Duration { get; set; }
}

59
StabilityMatrix.Avalonia/Animations/BetterDrillInNavigationTransition.cs

@ -0,0 +1,59 @@
using System;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterDrillInNavigationTransition : BaseTransitionInfo
{
/// <summary>
/// Gets or sets whether the animation should drill in (false) or drill out (true)
/// </summary>
public bool IsReversed { get; set; } = false; //Zoom out if true
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(400);
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 0.0),
new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.5 : 0.0),
new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.5 : 0.0)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1.0),
new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.0 : 1.0),
new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.0 : 1.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visualCtrl)
{
visualCtrl.Opacity = 1;
}
}
}

66
StabilityMatrix.Avalonia/Animations/BetterEntranceNavigationTransition.cs

@ -0,0 +1,66 @@
using System;
using System.Threading;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterEntranceNavigationTransition : BaseTransitionInfo
{
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(500);
/// <summary>
/// Gets or sets the Horizontal Offset used when animating
/// </summary>
public double FromHorizontalOffset { get; set; } = 0;
/// <summary>
/// Gets or sets the Vertical Offset used when animating
/// </summary>
public double FromVerticalOffset { get; set; } = 100;
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 0.0),
new Setter(TranslateTransform.XProperty,FromHorizontalOffset),
new Setter(TranslateTransform.YProperty, FromVerticalOffset)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1d),
new Setter(TranslateTransform.XProperty,0.0),
new Setter(TranslateTransform.YProperty, 0.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visualCtrl)
{
visualCtrl.Opacity = 1;
}
}
}

96
StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs

@ -0,0 +1,96 @@
using System;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterSlideNavigationTransition : BaseTransitionInfo
{
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(167);
/// <summary>
/// Gets or sets the type of animation effect to play during the slide transition.
/// </summary>
public SlideNavigationTransitionEffect Effect { get; set; } = SlideNavigationTransitionEffect.FromRight;
/// <summary>
/// Gets or sets the HorizontalOffset used when animating from the Left or Right
/// </summary>
public double FromHorizontalOffset { get; set; } = 56;
/// <summary>
/// Gets or sets the VerticalOffset used when animating from the Top or Bottom
/// </summary>
public double FromVerticalOffset { get; set; } = 56;
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
double length = 0;
bool isVertical = false;
switch (Effect)
{
case SlideNavigationTransitionEffect.FromLeft:
length = -FromHorizontalOffset;
break;
case SlideNavigationTransitionEffect.FromRight:
length = FromHorizontalOffset;
break;
case SlideNavigationTransitionEffect.FromTop:
length = -FromVerticalOffset;
isVertical = true;
break;
case SlideNavigationTransitionEffect.FromBottom:
length = FromVerticalOffset;
isVertical = true;
break;
}
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, length),
new Setter(Visual.OpacityProperty, 0d)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters=
{
new Setter(Visual.OpacityProperty, 1d)
},
Cue = new Cue(0.05d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1d),
new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, 0.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visual)
{
visual.Opacity = 1;
}
}
}

37
StabilityMatrix.Avalonia/Services/NavigationService.cs

@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Navigation;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.Services;
@ -26,6 +30,23 @@ public class NavigationService : INavigationService
throw new InvalidOperationException("SetFrame was not called before NavigateTo.");
}
if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)
{
// Handle animation scale
switch (transitionInfo)
{
// If the transition info is null or animation scale is 0, suppress the transition
case null:
case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:
transitionInfo = new SuppressNavigationTransitionInfo();
break;
case BaseTransitionInfo baseTransitionInfo:
baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;
break;
}
}
_frame.NavigateToType(typeof(TViewModel),
null,
new FrameNavigationOptions
@ -52,6 +73,22 @@ public class NavigationService : INavigationService
throw new InvalidOperationException("SetFrame was not called before NavigateTo.");
}
if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)
{
// Handle animation scale
switch (transitionInfo)
{
// If the transition info is null or animation scale is 0, suppress the transition
case null:
case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:
transitionInfo = new SuppressNavigationTransitionInfo();
break;
case BaseTransitionInfo baseTransitionInfo:
baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;
break;
}
}
_frame.NavigateFromObject(viewModel,
new FrameNavigationOptions
{

19
StabilityMatrix.Avalonia/ViewLocator.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using FluentAvalonia.UI.Controls;
@ -51,28 +52,30 @@ public class ViewLocator : IDataTemplate, INavigationPageFactory
/// <inheritdoc />
public Control? GetPage(Type srcType)
{
if (Attribute.GetCustomAttribute(srcType, typeof(ViewAttribute)) is ViewAttribute viewAttr)
if (Attribute.GetCustomAttribute(srcType, typeof(ViewAttribute)) is not ViewAttribute
viewAttr)
{
throw new InvalidOperationException("View not found for " + srcType.FullName);
}
var viewType = viewAttr.GetViewType();
var view = GetView(viewType);
view.DataContext ??= App.Services.GetService(srcType);
return view;
}
throw new InvalidOperationException("View not found for " + srcType.FullName);
}
/// <inheritdoc />
public Control GetPageFromObject(object target)
{
if (Attribute.GetCustomAttribute(target.GetType(), typeof(ViewAttribute)) is ViewAttribute viewAttr)
if (Attribute.GetCustomAttribute(target.GetType(), typeof(ViewAttribute)) is not
ViewAttribute viewAttr)
{
throw new InvalidOperationException("View not found for " + target.GetType().FullName);
}
var viewType = viewAttr.GetViewType();
var view = GetView(viewType);
view.DataContext ??= target;
return view;
}
throw new InvalidOperationException("View not found for " + target.GetType().FullName);
}
}

3
StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs

@ -38,7 +38,6 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
private readonly IPyRunner pyRunner;
private readonly IDownloadService downloadService;
private readonly INotificationService notificationService;
private readonly ISharedFolders sharedFolders;
private readonly IPrerequisiteHelper prerequisiteHelper;
[ObservableProperty] private BasePackage selectedPackage;
@ -82,14 +81,12 @@ public partial class InstallerViewModel : ContentDialogViewModelBase
IPackageFactory packageFactory,
IPyRunner pyRunner,
IDownloadService downloadService, INotificationService notificationService,
ISharedFolders sharedFolders,
IPrerequisiteHelper prerequisiteHelper)
{
this.settingsManager = settingsManager;
this.pyRunner = pyRunner;
this.downloadService = downloadService;
this.notificationService = notificationService;
this.sharedFolders = sharedFolders;
this.prerequisiteHelper = prerequisiteHelper;
// AvailablePackages and SelectedPackage

55
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

@ -1,16 +1,17 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using NLog;
using Polly;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
@ -20,7 +21,7 @@ using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
public partial class PackageCardViewModel : Base.ProgressViewModel
public partial class PackageCardViewModel : ProgressViewModel
{
private readonly IPackageFactory packageFactory;
private readonly INotificationService notificationService;
@ -67,7 +68,7 @@ public partial class PackageCardViewModel : Base.ProgressViewModel
settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
navigationService.NavigateTo<LaunchPageViewModel>(new EntranceNavigationTransitionInfo());
navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());
EventManager.Instance.OnPackageLaunchRequested(Package.Id);
}
@ -117,17 +118,23 @@ public partial class PackageCardViewModel : Base.ProgressViewModel
public async Task Update()
{
if (Package == null) return;
var basePackage = packageFactory[Package.PackageName!];
if (basePackage == null)
{
logger.Error("Could not find package {SelectedPackagePackageName}",
logger.Warn("Could not find package {SelectedPackagePackageName}",
Package.PackageName);
notificationService.Show("Invalid Package type",
$"Package {Package.PackageName.ToRepr()} is not a valid package type",
NotificationType.Error);
return;
}
Text = $"Updating {Package.DisplayName}";
IsIndeterminate = true;
try
{
basePackage.InstallLocation = Package.FullPath!;
var progress = new Progress<ProgressReport>(progress =>
{
@ -142,40 +149,30 @@ public partial class PackageCardViewModel : Base.ProgressViewModel
var updateResult = await basePackage.Update(Package, progress);
if (string.IsNullOrWhiteSpace(updateResult))
{
var errorMsg =
$"There was an error updating {Package.DisplayName}. Please try again later.";
if (Package.PackageName == "automatic")
{
errorMsg = errorMsg.Replace("Please try again later.",
"Please stash any changes before updating, or manually update the package.");
}
// there was an error
notificationService.Show(new Notification("Error updating package",
errorMsg, NotificationType.Error));
}
settingsManager.UpdatePackageVersionNumber(Package.Id, updateResult);
notificationService.Show("Update complete",
$"{Package.DisplayName} has been updated to the latest version.",
NotificationType.Success);
Dispatcher.UIThread.Invoke(() =>
{
using (settingsManager.BeginTransaction())
await using (settingsManager.BeginTransaction())
{
Package.UpdateAvailable = false;
}
IsUpdateAvailable = false;
InstalledVersion = Package.DisplayVersion ?? "Unknown";
}
catch (Exception e)
{
logger.Error(e, "Error Updating Package ({PackageName})", basePackage.Name);
notificationService.ShowPersistent($"Error Updating {Package.DisplayName}", e.Message, NotificationType.Error);
}
finally
{
IsIndeterminate = false;
InstalledVersion = settingsManager.Settings.InstalledPackages
.First(x => x.Id == Package.Id).DisplayVersion ?? "Unknown";
Value = 0;
});
Text = "";
}
}
public async Task OpenFolder()

19
StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs

@ -73,6 +73,21 @@ public partial class SettingsViewModel : PageViewModelBase
"System",
};
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;
@ -110,6 +125,7 @@ public partial class SettingsViewModel : PageViewModelBase
SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1];
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;
SelectedAnimationScale = settingsManager.Settings.AnimationScale;
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedTheme,
@ -120,6 +136,9 @@ public partial class SettingsViewModel : PageViewModelBase
settings => settings.IsDiscordRichPresenceEnabled);
DebugThrowAsyncExceptionCommand.WithNotificationErrorHandler(notificationService, LogLevel.Warn);
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedAnimationScale,
settings => settings.AnimationScale);
}
partial void OnSelectedThemeChanged(string? value)

2
StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml

@ -148,7 +148,7 @@
<StackPanel Margin="0,16,0,0" Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Label Content="{Binding ReleaseLabelText, FallbackValue=Version}" />
<Label Content="{Binding ReleaseLabelText}" />
<ComboBox
ItemsSource="{Binding AvailableVersions}"
MinWidth="200"

3
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -20,6 +20,7 @@ using FluentAvalonia.UI.Media;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Windowing;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
@ -141,7 +142,7 @@ public partial class MainWindow : AppWindowBase
{
throw new InvalidOperationException($"NavigationViewItem Tag must be of type ViewModelBase, not {nvi.Tag?.GetType()}");
}
navigationService.NavigateTo(vm, e.RecommendedNavigationTransitionInfo);
navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
}
}

2
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -162,7 +162,7 @@
Margin="8,8,8,0"
TextAlignment="Center"
Width="180"
Text="{Binding Text, TargetNullValue=Updating so and so to the latest version... 5%}"
Text="{Binding Text}"
TextWrapping="Wrap"
VerticalAlignment="Center" />
</Grid>

112
StabilityMatrix.Avalonia/Views/SettingsPage.axaml

@ -14,46 +14,44 @@
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<StackPanel Spacing="2" Margin="12,16">
<Grid RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto"
Margin="8, 16">
<!-- Theme -->
<Grid RowDefinitions="auto,*">
<Grid Grid.Row="0" RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="Appearance"
Margin="0,0,0,8" />
<StackPanel Grid.Row="1">
<ui:SettingsExpander
Grid.Row="1"
Header="Theme"
IconSource="WeatherMoon"
Margin="8,0">
Margin="8,0,8,4">
<ui:SettingsExpander.Footer>
<ComboBox
ItemsSource="{Binding AvailableThemes}"
SelectedItem="{Binding SelectedTheme}"
Margin="8"
MinWidth="100"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</Grid>
<!-- TODO: Text2Image host port settings -->
<!-- Checkpoints Manager Options -->
<Grid RowDefinitions="auto,*,Auto">
<Grid Grid.Row="1" Margin="0,8,0,0" RowDefinitions="auto,*,Auto">
<TextBlock
FontWeight="Medium"
Text="Checkpoint Manager"
Margin="0,16,0,8" />
Margin="0,0,0,8" />
<ui:SettingsExpander
Grid.Row="1"
IconSource="Folder"
Header="Remove shared checkpoints directory symbolic links on shutdown"
Description="Select this option if you're having problems moving Stability Matrix to another drive"
Margin="8,8">
Margin="8,0">
<ui:SettingsExpander.Footer>
<CheckBox Margin="4"
<CheckBox Margin="8"
IsChecked="{Binding RemoveSymlinksOnShutdown}"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
@ -62,35 +60,34 @@
IconSource="Refresh"
Header="Reset Checkpoints Cache"
Description="Rebuilds the installed checkpoints cache. Use if checkpoints are incorrectly labeled in the Model Browser."
Margin="8,0">
Margin="8, 4">
<ui:SettingsExpander.Footer>
<Button Margin="8"
Command="{Binding ResetCheckpointCache}"
<Button Command="{Binding ResetCheckpointCache}"
Content="Reset Checkpoints Cache"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Grid>
<!-- Environment Options -->
<Grid RowDefinitions="auto,*">
<Grid Grid.Row="2" Margin="0,8,0,0" RowDefinitions="Auto, Auto, Auto">
<TextBlock
FontWeight="Medium"
Text="Package Environment"
Margin="0,16,0,0" />
Margin="0,0,0,8" />
<StackPanel Grid.Row="1" Margin="0,8" Spacing="4">
<ui:SettingsExpander
<ui:SettingsExpander Grid.Row="1"
Header="Environment Variables"
IconSource="OtherUser"
Margin="8,0">
<ui:SettingsExpander.Footer>
<Button Content="Edit" Command="{Binding OpenEnvVarsDialogCommand}"/>
<Button Content="Edit"
Command="{Binding OpenEnvVarsDialogCommand}"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander
<ui:SettingsExpander Grid.Row="2"
Header="Embedded Python"
Margin="8,0">
Margin="8,4">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-python"/>
</ui:SettingsExpander.IconSource>
@ -105,44 +102,40 @@
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</Grid>
<!-- Integrations -->
<Grid RowDefinitions="auto,*">
<Grid Grid.Row="3" Margin="0,8,0,0" RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="Integrations"
Margin="0,16,0,8" />
<StackPanel Grid.Row="1">
<ui:SettingsExpander
Margin="0,0,0,8" />
<ui:SettingsExpander Grid.Row="1"
Header="Discord Rich Presence"
Margin="8,0">
Margin="8,0,8,4">
<ui:SettingsExpander.IconSource>
<controls:FASymbolIconSource Symbol="fa-brands fa-discord"/>
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch
IsChecked="{Binding IsDiscordRichPresenceEnabled}"
Margin="4" />
IsChecked="{Binding IsDiscordRichPresenceEnabled}" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</Grid>
<!-- System Options -->
<Grid RowDefinitions="auto,*">
<Grid Grid.Row="4" Margin="0,8,0,0" RowDefinitions="auto,*">
<TextBlock
FontWeight="Medium"
Text="System"
Margin="0,16,0,0" />
Margin="0,0,0,8" />
<ui:SettingsExpander
Grid.Row="1"
ToolTip.Tip="{OnPlatform Default='Only available on Windows', Windows={x:Null}}"
Header="Add Stability Matrix to the Start Menu"
Description="Uses the current app location, you can run this again if you move the app"
IconSource="StarAdd"
Margin="8">
Margin="8,0,8,4">
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<controls:ProgressRing
@ -176,57 +169,84 @@
</Grid>
<!-- Debug Options -->
<Grid RowDefinitions="auto,*"
Margin="0,4,0,0"
<Grid Grid.Row="5" RowDefinitions="auto,*"
Margin="0,8,0,0"
IsVisible="{Binding SharedState.IsDebugMode}" >
<TextBlock
FontWeight="Medium"
Text="Debug Options"
Margin="0,16,0,0" />
Margin="0,0,0,8" />
<ui:SettingsExpander
Grid.Row="1"
IconSource="Code"
Command="{Binding LoadDebugInfo}"
Header="Debug Options"
Margin="8">
Margin="8, 0,8,4">
<ui:SettingsExpanderItem Description="Paths" IconSource="Folder">
<ui:SettingsExpanderItem Description="Paths" IconSource="Folder"
Margin="4">
<SelectableTextBlock Text="{Binding DebugPaths}"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
TextWrapping="WrapWithOverflow" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Description="Compat Info" IconSource="StarFilled">
<ui:SettingsExpanderItem Description="Compat Info" IconSource="StarFilled"
Margin="4,0,4,4">
<SelectableTextBlock Text="{Binding DebugCompatInfo}"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
TextWrapping="WrapWithOverflow" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Description="GPU Info" IconSource="FullScreenMaximize">
<ui:SettingsExpanderItem Description="GPU Info" IconSource="FullScreenMaximize"
Margin="4,0,4,4">
<SelectableTextBlock Text="{Binding DebugGpuInfo}"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
TextWrapping="WrapWithOverflow" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Notification" IconSource="CommentAdd">
<ui:SettingsExpanderItem Content="Animation Scale" IconSource="Clock"
Description="Lower values = faster animations. 0x means animations are instant."
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>
<ComboBox Margin="0, 8"
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 Content="Notification" IconSource="CommentAdd"
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>
<Button
Margin="0, 8"
Command="{Binding DebugNotificationCommand}"
Content="New Notification"/>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Content Dialog" IconSource="NewWindow">
<ui:SettingsExpanderItem Content="Content Dialog" IconSource="NewWindow"
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>
<Button
Margin="0, 8"
Command="{Binding DebugContentDialogCommand}"
Content="Show Dialog"/>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Exceptions" IconSource="Flag">
<ui:SettingsExpanderItem Content="Exceptions" IconSource="Flag"
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>
<SplitButton
Margin="0, 8"
Command="{Binding DebugThrowExceptionCommand}"
Content="Command Exception">
@ -269,7 +289,7 @@
<!-- TODO: Directories card -->
<Grid RowDefinitions="auto,*" Margin="0,4,0,0">
<Grid Grid.Row="6" RowDefinitions="auto,*" Margin="0,4,0,0">
<StackPanel
Grid.Row="1"
HorizontalAlignment="Left"
@ -316,9 +336,7 @@
</Grid>
<!-- Extra space at the bottom -->
<Panel Margin="0,0,0,16" />
</StackPanel>
<Panel Grid.Row="7" Margin="0,0,0,16" />
</Grid>
</ScrollViewer>
</Grid>

8
StabilityMatrix.Core/Helper/PrerequisiteHelper.cs

@ -56,14 +56,14 @@ public class PrerequisiteHelper : IPrerequisiteHelper
public async Task RunGit(string? workingDirectory = null, params string[] args)
{
var process = ProcessRunner.StartAnsiProcess(GitExePath, args, workingDirectory: workingDirectory);
await ProcessRunner.WaitForExitConditionAsync(process);
await ProcessRunner.WaitForExitConditionAsync(process).ConfigureAwait(false);
}
public async Task<string> GetGitOutput(string? workingDirectory = null, params string[] args)
{
var process = await ProcessRunner.GetProcessOutputAsync(GitExePath, string.Join(" ", args),
workingDirectory: workingDirectory);
return process;
var output = await ProcessRunner.GetProcessOutputAsync(GitExePath, string.Join(" ", args),
workingDirectory: workingDirectory).ConfigureAwait(false);
return output;
}
public async Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null)

6
StabilityMatrix.Core/Models/PackageVersion.cs

@ -1,7 +1,7 @@
namespace StabilityMatrix.Core.Models;
public class PackageVersion
public record PackageVersion
{
public string TagName { get; set; }
public string ReleaseNotesMarkdown { get; set; }
public required string TagName { get; set; }
public string? ReleaseNotesMarkdown { get; set; }
}

16
StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs

@ -240,6 +240,7 @@ public abstract class BaseGitPackage : BasePackage
public override async Task<string> Update(InstalledPackage installedPackage,
IProgress<ProgressReport>? progress = null, bool includePrerelease = false)
{
// Release mode
if (string.IsNullOrWhiteSpace(installedPackage.InstalledBranch))
{
var releases = await GetAllReleases().ConfigureAwait(false);
@ -248,16 +249,21 @@ public abstract class BaseGitPackage : BasePackage
await InstallPackage(progress).ConfigureAwait(false);
return latestRelease.TagName;
}
else
{
// Commit mode
var allCommits = await GetAllCommits(
installedPackage.InstalledBranch).ConfigureAwait(false);
var latestCommit = allCommits.First();
await DownloadPackage(latestCommit.Sha, true, progress);
var latestCommit = allCommits?.First();
if (latestCommit is null || string.IsNullOrEmpty(latestCommit.Sha))
{
throw new Exception("No commits found for branch");
}
await DownloadPackage(latestCommit.Sha, true, progress).ConfigureAwait(false);
await InstallPackage(progress).ConfigureAwait(false);
return latestCommit.Sha;
}
}
// Send input to the running process.
public virtual void SendInput(string input)

2
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -92,7 +92,7 @@ public class ComfyUI : BaseGitPackage
public override async Task<IEnumerable<PackageVersion>> GetAllVersions(bool isReleaseMode = true)
{
var allBranches = await GetAllBranches();
var allBranches = await GetAllBranches().ConfigureAwait(false);
return allBranches.Select(b => new PackageVersion
{
TagName = $"{b.Name}",

128
StabilityMatrix.Core/Models/Packages/InvokeAI.cs

@ -56,41 +56,10 @@ public class InvokeAI : BaseGitPackage
public override Dictionary<SharedFolderType, IReadOnlyList<string>> SharedFolders => new()
{
[SharedFolderType.StableDiffusion] = new[]
{
RelativeRootPath + "/models/sd-1/main",
RelativeRootPath + "/models/sd-2/main",
RelativeRootPath + "/models/sdxl/main",
RelativeRootPath + "/models/sdxl-refiner/main",
},
[SharedFolderType.Lora] = new[]
{
RelativeRootPath + "/models/sd-1/lora",
RelativeRootPath + "/models/sd-2/lora",
RelativeRootPath + "/models/sdxl/lora",
RelativeRootPath + "/models/sdxl-refiner/lora",
},
[SharedFolderType.TextualInversion] = new[]
{
RelativeRootPath + "/models/sd-1/embedding",
RelativeRootPath + "/models/sd-2/embedding",
RelativeRootPath + "/models/sdxl/embedding",
RelativeRootPath + "/models/sdxl-refiner/embedding",
},
[SharedFolderType.VAE] = new[]
{
RelativeRootPath + "/models/sd-1/vae",
RelativeRootPath + "/models/sd-2/vae",
RelativeRootPath + "/models/sdxl/vae",
RelativeRootPath + "/models/sdxl-refiner/vae",
},
[SharedFolderType.ControlNet] = new[]
{
RelativeRootPath + "/models/sd-1/controlnet",
RelativeRootPath + "/models/sd-2/controlnet",
RelativeRootPath + "/models/sdxl/controlnet",
RelativeRootPath + "/models/sdxl-refiner/controlnet",
},
[SharedFolderType.StableDiffusion] = new[] { RelativeRootPath + "/autoimport/main" },
[SharedFolderType.Lora] = new[] { RelativeRootPath + "/autoimport/lora" },
[SharedFolderType.TextualInversion] = new[] { RelativeRootPath + "/autoimport/embedding" },
[SharedFolderType.ControlNet] = new[] { RelativeRootPath + "/autoimport/controlnet" },
};
// https://github.com/invoke-ai/InvokeAI/blob/main/docs/features/CONFIGURATION.md
@ -216,6 +185,11 @@ public class InvokeAI : BaseGitPackage
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
if (installedPackage.FullPath is null)
{
throw new NullReferenceException("Installed package path is null");
}
await using var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath, "venv"));
venvRunner.WorkingDirectory = installedPackage.FullPath;
venvRunner.EnvironmentVariables = GetEnvVars(installedPackage.FullPath);
@ -355,92 +329,20 @@ public class InvokeAI : BaseGitPackage
public override Task SetupModelFolders(DirectoryPath installDirectory)
{
var modelsFolder = SettingsManager.ModelsDirectory;
if (!Directory.Exists(modelsFolder))
StabilityMatrix.Core.Helper.SharedFolders
.SetupLinks(SharedFolders, SettingsManager.ModelsDirectory, installDirectory);
return Task.CompletedTask;
var connectedModelJsons =
Directory.GetFiles(modelsFolder, "*.cm-info.json", SearchOption.AllDirectories);
var connectedModelDict = new Dictionary<string, ConnectedModelInfo>();
foreach (var jsonFile in connectedModelJsons)
{
var json = File.ReadAllText(jsonFile);
var connectedModelInfo = JsonSerializer.Deserialize<ConnectedModelInfo>(json);
var extension = connectedModelInfo?.FileMetadata.Format switch
{
CivitModelFormat.SafeTensor => ".safetensors",
CivitModelFormat.PickleTensor => ".pt",
_ => string.Empty
};
if (string.IsNullOrWhiteSpace(extension) || connectedModelInfo == null)
continue;
var modelFilePath = jsonFile.Replace(".cm-info.json", extension);
if (File.Exists(modelFilePath))
{
connectedModelDict[modelFilePath] = connectedModelInfo;
}
}
foreach (var modelFilePath in connectedModelDict.Keys)
{
var model = connectedModelDict[modelFilePath];
var modelType = model.ModelType switch
public override async Task UpdateModelFolders(DirectoryPath installDirectory)
{
CivitModelType.Checkpoint => "main",
CivitModelType.LORA => "lora",
CivitModelType.TextualInversion => "embedding",
CivitModelType.Controlnet => "controlnet",
_ => string.Empty
};
if (string.IsNullOrWhiteSpace(modelType))
continue;
var sourcePath = modelFilePath;
var destinationPath = Path.Combine(installDirectory, RelativeRootPath, "autoimport",
modelType, Path.GetFileName(modelFilePath));
try
{
File.CreateSymbolicLink(destinationPath, sourcePath);
}
catch (IOException e)
{
// File already exists
Logger.Warn(e,
$"Could not create symlink for {sourcePath} to {destinationPath} - file already exists");
}
}
return Task.CompletedTask;
await StabilityMatrix.Core.Helper.SharedFolders.UpdateLinksForPackage(this,
SettingsManager.ModelsDirectory, installDirectory).ConfigureAwait(false);
}
public override Task UpdateModelFolders(DirectoryPath installDirectory) =>
SetupModelFolders(installDirectory);
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory)
{
var autoImportDir = Path.Combine(installDirectory, RelativeRootPath, "autoimport");
var allSymlinks =
Directory.GetFiles(autoImportDir, "*.*", SearchOption.AllDirectories)
.Select(path => new FileInfo(path)).Where(file => file.LinkTarget != null);
foreach (var link in allSymlinks)
{
try
{
link.Delete();
}
catch (IOException e)
{
Logger.Warn(e, $"Could not delete symlink {link.FullName}");
}
}
StabilityMatrix.Core.Helper.SharedFolders.RemoveLinksForPackage(this, installDirectory);
return Task.CompletedTask;
}

35
StabilityMatrix.Core/Models/Packages/VladAutomatic.cs

@ -303,45 +303,42 @@ public class VladAutomatic : BaseGitPackage
public override async Task<string> Update(InstalledPackage installedPackage,
IProgress<ProgressReport>? progress = null, bool includePrerelease = false)
{
if (installedPackage.InstalledBranch is null)
{
throw new Exception("Installed branch is null");
}
progress?.Report(new ProgressReport(0.1f, message: "Downloading package update...",
isIndeterminate: true, type: ProgressType.Download));
var version = await GithubApi.GetAllCommits(Author, Name, installedPackage.InstalledBranch);
var version = await GithubApi.GetAllCommits(Author, Name, installedPackage.InstalledBranch).ConfigureAwait(false);
var latest = version?.FirstOrDefault();
if (latest == null)
if (latest?.Sha is null)
{
Logger.Warn("No latest version found for vlad");
return string.Empty;
throw new Exception("Could not get latest version");
}
try
{
var output =
await PrerequisiteHelper.GetGitOutput(workingDirectory: installedPackage.FullPath,
"rev-parse", "HEAD");
await PrerequisiteHelper
.GetGitOutput(installedPackage.FullPath, "rev-parse", "HEAD")
.ConfigureAwait(false);
if (output?.Replace("\n", "") == latest.Sha)
if (output.Replace(Environment.NewLine, "") == latest.Sha)
{
return latest.Sha;
}
}
catch (Exception)
{
// ignored
}
try
{
await PrerequisiteHelper.RunGit(workingDirectory: installedPackage.FullPath, "pull",
"origin", installedPackage.InstalledBranch);
}
catch (Exception e)
{
Logger.Log(LogLevel.Error, e);
return string.Empty;
Logger.Warn(e, "Could not get current git hash, continuing with update");
}
await PrerequisiteHelper.RunGit(installedPackage.FullPath, "pull",
"origin", installedPackage.InstalledBranch).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, message: "Update Complete", isIndeterminate: false,
type: ProgressType.Generic));

2
StabilityMatrix.Core/Models/Settings/Settings.cs

@ -51,6 +51,8 @@ public class Settings
public HashSet<string>? InstalledModelHashes { get; set; } = new();
public float AnimationScale { get; set; } = 1.0f;
public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)
{
RemoveInstalledPackageAndUpdateActive(package.Id);

Loading…
Cancel
Save