diff --git a/Avalonia.Gif/WebpInstance.cs b/Avalonia.Gif/WebpInstance.cs
index b92569349..8ac596f34 100644
--- a/Avalonia.Gif/WebpInstance.cs
+++ b/Avalonia.Gif/WebpInstance.cs
@@ -54,14 +54,18 @@ public WebpInstance(Stream currentStream)
var pixSize = new PixelSize(_codec.Info.Width, _codec.Info.Height);
- _targetBitmap = new WriteableBitmap(pixSize, new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Opaque);
+ _targetBitmap = new WriteableBitmap(
+ pixSize,
+ new Vector(96, 96),
+ PixelFormat.Bgra8888,
+ AlphaFormat.Opaque
+ );
GifPixelSize = pixSize;
_totalTime = TimeSpan.Zero;
_frameTimes = _codec
- .FrameInfo
- .Select(frame =>
+ .FrameInfo.Select(frame =>
{
_totalTime = _totalTime.Add(TimeSpan.FromMilliseconds(frame.Duration));
return _totalTime;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1b8fb1ed..4c94d0513 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
+## v2.13.1
+### Changed
+- Redesigned the Checkpoint Manager Filter flyout to include more options and improve the layout
+- "Clear All" button will now remain at the top of the Downloads list regardless of scroll position - thanks to @Genteure!
+- Improved image metadata parsing - thanks to @Genteure!
+### Fixed
+- Fixed [#1078](https://github.com/LykosAI/StabilityMatrix/issues/1078) - "Call from invalid thread" error after one-click install finishes
+- Fixed [#1080](https://github.com/LykosAI/StabilityMatrix/issues/1080) - Some models not displayed in Checkpoint Manager
+- Fixed Inference image selector card buttons taking up the whole height of the card
+- Fixed Inference mask editor failing to paint to the right-most edge on large images
+- Fixed Inference mask editor not showing the entire image in certain circumstances
+- Fixed crash when dragging & dropping images in Inference (hopefully)
+- Fixed an issue where certain sampler/scheduler combos would not get saved in image metadata - thanks to @yansigit!
+### Supporters
+#### Visionaries
+- A heartfelt thank you to our exceptional Visionary-tier Patreon backers, **Waterclouds** and **TheTekknician**! We truly appreciate your steadfast support!
+#### Pioneers
+- We are also very grateful to our wonderful Pioneer-tier Patreon supporters, **tankfox**, **Mr Unknown**, **Szir777**, **Tigon**, and **NowFallenAngel**! Your support means a lot to us!
+
## v2.13.0
### Added
- Added new package - [ComfyUI-Zluda](https://github.com/patientx/ComfyUI-Zluda) - for AMD GPU users on Windows
diff --git a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml
index 0aa53b84c..bf6de24cc 100644
--- a/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml
+++ b/StabilityMatrix.Avalonia/Controls/Inference/SelectImageCard.axaml
@@ -48,7 +48,7 @@
Source="{Binding ImageSource}"
Stretch="Uniform"
StretchDirection="Both" />
-
+
@@ -57,13 +57,13 @@
-
+ RenderOptions.BitmapInterpolationMode="HighQuality"
+ Source="{Binding CachedOrNewMaskRenderImage.Bitmap}"
+ Stretch="Uniform"
+ StretchDirection="Both" />
@@ -102,16 +102,18 @@
-
+ IsVisible="{Binding !IsSelectionAvailable}"
+ Orientation="Horizontal"
+ Spacing="4">
+
+ CornerRadius="10"
+ IsVisible="{Binding IsMaskEditorEnabled}">
+ FontSize="{TemplateBinding FontSize}"
+ IsChecked="{Binding IsMaskOverlayEnabled}">
@@ -142,11 +144,13 @@
Symbol="Eye" />
-
+
+ CornerRadius="10"
+ IsVisible="{Binding IsMaskEditorEnabled}">
-
+
diff --git a/StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml.cs b/StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml.cs
index 85cd915e9..3cbbe0e52 100644
--- a/StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml.cs
+++ b/StabilityMatrix.Avalonia/Controls/Painting/PaintCanvas.axaml.cs
@@ -260,7 +260,7 @@ private void HandlePointerMoved(PointerEventArgs e)
// penPath.Path.LineTo(cursorPosition.ToSKPoint());
// Get bounds for discarding invalid points
- var canvasBounds = MainCanvas?.Bounds ?? new Rect();
+ var canvasBounds = new Rect(0, 0, MainCanvas?.Bounds.Width ?? 0, MainCanvas?.Bounds.Height ?? 0);
// Add points
foreach (var point in points)
diff --git a/StabilityMatrix.Avalonia/Models/ImageSource.cs b/StabilityMatrix.Avalonia/Models/ImageSource.cs
index df913b042..e0739cfa0 100644
--- a/StabilityMatrix.Avalonia/Models/ImageSource.cs
+++ b/StabilityMatrix.Avalonia/Models/ImageSource.cs
@@ -277,7 +277,7 @@ protected virtual void Dispose(bool disposing)
if (!disposing)
return;
- Bitmap?.Dispose();
+ Bitmap = null;
}
///
diff --git a/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs b/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs
new file mode 100644
index 000000000..9f91ff253
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs
@@ -0,0 +1,92 @@
+using System.Text.Json.Nodes;
+using Injectio.Attributes;
+using Microsoft.Extensions.Logging;
+using StabilityMatrix.Core.Api;
+using StabilityMatrix.Core.Database;
+using StabilityMatrix.Core.Models.Api;
+using StabilityMatrix.Core.Models.Database;
+
+namespace StabilityMatrix.Avalonia.Services;
+
+[RegisterSingleton]
+public class CivitBaseModelTypeService(
+ ILogger logger,
+ ICivitApi civitApi,
+ ILiteDbContext dbContext
+) : ICivitBaseModelTypeService
+{
+ private const string CacheId = "BaseModelTypes";
+ private static readonly TimeSpan CacheExpiration = TimeSpan.FromHours(24);
+
+ ///
+ /// Gets the list of base model types, using cache if available and not expired
+ ///
+ public async Task> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true)
+ {
+ List civitBaseModels = [];
+
+ if (!forceRefresh)
+ {
+ var cached = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId);
+ if (cached != null && DateTimeOffset.UtcNow.Subtract(cached.CreatedAt) < CacheExpiration)
+ {
+ civitBaseModels = cached.ModelTypes;
+ }
+ }
+
+ try
+ {
+ if (civitBaseModels.Count <= 0)
+ {
+ var baseModelsResponse = await civitApi.GetBaseModelList();
+ var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync();
+ var baseModels = JsonNode.Parse(jsonContent);
+
+ var jArray =
+ baseModels?["error"]?["issues"]?[0]?["unionErrors"]?[0]?["issues"]?[0]?["options"]
+ as JsonArray;
+
+ civitBaseModels = jArray?.GetValues().ToList() ?? [];
+
+ // Cache the results
+ var cacheEntry = new CivitBaseModelTypeCacheEntry
+ {
+ Id = CacheId,
+ ModelTypes = civitBaseModels,
+ CreatedAt = DateTimeOffset.UtcNow
+ };
+
+ await dbContext.UpsertCivitBaseModelTypeCacheEntry(cacheEntry);
+ }
+
+ if (includeAllOption)
+ {
+ civitBaseModels.Insert(0, CivitBaseModelType.All.ToString());
+ }
+
+ // Filter and sort results
+ var filteredResults = civitBaseModels
+ .Where(s => !s.Equals("odor", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(s => s)
+ .ToList();
+
+ return filteredResults;
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, "Failed to get base model list");
+
+ // Return cached results if available, even if expired
+ var expiredCache = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId);
+ return expiredCache?.ModelTypes ?? [];
+ }
+ }
+
+ ///
+ /// Clears the cached base model types
+ ///
+ public void ClearCache()
+ {
+ dbContext.CivitBaseModelTypeCache.DeleteAllAsync();
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs b/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs
new file mode 100644
index 000000000..9dc58a804
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Services/ICivitBaseModelTypeService.cs
@@ -0,0 +1,7 @@
+namespace StabilityMatrix.Avalonia.Services;
+
+public interface ICivitBaseModelTypeService
+{
+ Task> GetBaseModelTypes(bool forceRefresh = false, bool includeAllOption = true);
+ void ClearCache();
+}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
index 01be71b4c..0e3a985f1 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs
@@ -421,7 +421,7 @@ await notificationService.ShowAsync(
// Clear progress
OutputProgress.ClearProgress();
- ImageGalleryCardViewModel.PreviewImage?.Dispose();
+ // ImageGalleryCardViewModel.PreviewImage?.Dispose();
ImageGalleryCardViewModel.PreviewImage = null;
ImageGalleryCardViewModel.IsPreviewOverlayEnabled = false;
diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs
index c1261a8ce..3d1da920b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Base/PageViewModelBase.cs
@@ -5,7 +5,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base;
///
/// An abstract class for enabling page navigation.
///
-public abstract class PageViewModelBase : ViewModelBase
+public abstract class PageViewModelBase : DisposableViewModelBase
{
///
/// Gets if the user can navigate to the next page
@@ -16,7 +16,7 @@ public abstract class PageViewModelBase : ViewModelBase
/// Gets if the user can navigate to the previous page
///
public virtual bool CanNavigatePrevious { get; protected set; }
-
+
public abstract string Title { get; }
public abstract IconSource IconSource { get; }
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
index 3f11591a0..309accaa2 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
@@ -45,6 +45,7 @@ public sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinit
private readonly ISettingsManager settingsManager;
private readonly ILiteDbContext liteDbContext;
private readonly INotificationService notificationService;
+ private readonly ICivitBaseModelTypeService baseModelTypeService;
private bool dontSearch = false;
private readonly SourceCache, int> modelCache = new(static ov => ov.Value.Id);
@@ -126,13 +127,15 @@ public CivitAiBrowserViewModel(
ISettingsManager settingsManager,
ServiceManager dialogFactory,
ILiteDbContext liteDbContext,
- INotificationService notificationService
+ INotificationService notificationService,
+ ICivitBaseModelTypeService baseModelTypeService
)
{
this.civitApi = civitApi;
this.settingsManager = settingsManager;
this.liteDbContext = liteDbContext;
this.notificationService = notificationService;
+ this.baseModelTypeService = baseModelTypeService;
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
@@ -727,31 +730,7 @@ private void UpdateResultsText()
[Localizable(false)]
private async Task> GetBaseModelList()
{
- try
- {
- var baseModelsResponse = await civitApi.GetBaseModelList();
- var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync();
- var baseModels = JsonNode.Parse(jsonContent);
-
- var jArray =
- baseModels?["error"]?["issues"]?[0]?["unionErrors"]?[0]?["issues"]?[0]?["options"]
- as JsonArray;
- var civitBaseModels = jArray?.GetValues().ToList() ?? [];
-
- civitBaseModels.Insert(0, CivitBaseModelType.All.ToString());
-
- var filteredResults = civitBaseModels
- .Where(s => s.Equals("odor", StringComparison.OrdinalIgnoreCase) == false)
- .OrderBy(s => s)
- .ToList();
-
- return filteredResults;
- }
- catch (Exception e)
- {
- Logger.Error(e, "Failed to get base model list");
- return [];
- }
+ return await baseModelTypeService.GetBaseModelTypes();
}
public override string Header => Resources.Label_CivitAi;
diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
index 360b73099..057d5ba83 100644
--- a/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
@@ -59,7 +59,8 @@ public partial class CheckpointsPageViewModel(
INotificationService notificationService,
IMetadataImportService metadataImportService,
IModelImportService modelImportService,
- ServiceManager dialogFactory
+ ServiceManager dialogFactory,
+ ICivitBaseModelTypeService baseModelTypeService
) : PageViewModelBase
{
public override string Title => Resources.Label_CheckpointManager;
@@ -116,14 +117,6 @@ ServiceManager dialogFactory
[ObservableProperty]
private bool isDragOver;
- [ObservableProperty]
- private ObservableCollection baseModelOptions =
- new(
- Enum.GetValues()
- .Where(x => x != CivitBaseModelType.All)
- .Select(x => x.GetStringValue())
- );
-
[ObservableProperty]
private ObservableCollection selectedBaseModels = [];
@@ -156,26 +149,65 @@ ServiceManager dialogFactory
? Resources.Label_OneImageSelected.Replace("images ", "")
: string.Format(Resources.Label_NumImagesSelected, NumItemsSelected).Replace("images ", "");
- protected override void OnInitialLoaded()
+ private SourceCache BaseModelCache { get; } = new(s => s);
+
+ public IObservableCollection BaseModelOptions { get; set; } =
+ new ObservableCollectionExtended();
+
+ protected override async Task OnInitialLoadedAsync()
{
if (Design.IsDesignMode)
return;
- base.OnInitialLoaded();
+ await base.OnInitialLoadedAsync();
+
+ var settingsSelectedBaseModels = settingsManager.Settings.SelectedBaseModels;
+
+ AddDisposable(
+ BaseModelCache
+ .Connect()
+ .DeferUntilLoaded()
+ .Transform(
+ baseModel =>
+ new BaseModelOptionViewModel
+ {
+ ModelType = baseModel,
+ IsSelected = settingsSelectedBaseModels.Contains(baseModel)
+ }
+ )
+ .Bind(BaseModelOptions)
+ .WhenPropertyChanged(p => p.IsSelected)
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe(next =>
+ {
+ if (next.Sender.IsSelected)
+ SelectedBaseModels.Add(next.Sender.ModelType);
+ else
+ SelectedBaseModels.Remove(next.Sender.ModelType);
+
+ OnPropertyChanged(nameof(ClearButtonText));
+ OnPropertyChanged(nameof(SelectedBaseModels));
+ })
+ );
- SelectedBaseModels = new ObservableCollection(BaseModelOptions);
- SelectedBaseModels.CollectionChanged += (_, _) =>
- {
- OnPropertyChanged(nameof(ClearButtonText));
- OnPropertyChanged(nameof(SelectedBaseModels));
- settingsManager.Transaction(
- settings => settings.SelectedBaseModels = SelectedBaseModels.ToList()
- );
- };
+ var settingsTransactionObservable = this.WhenPropertyChanged(x => x.SelectedBaseModels)
+ .Throttle(TimeSpan.FromMilliseconds(50))
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe(_ =>
+ {
+ settingsManager.Transaction(
+ settings => settings.SelectedBaseModels = SelectedBaseModels.ToList()
+ );
+ });
+
+ AddDisposable(settingsTransactionObservable);
+
+ var baseModelTypes = await baseModelTypeService.GetBaseModelTypes(includeAllOption: false);
+ BaseModelCache.EditDiff(baseModelTypes);
// Observable predicate from SearchQuery changes
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
- .Throttle(TimeSpan.FromMilliseconds(100))!
+ .Throttle(TimeSpan.FromMilliseconds(100))
.Select(
_ =>
(Func)(
@@ -228,9 +260,7 @@ or nameof(SortConnectedModelsFirst)
{
var comparer = new SortExpressionComparer();
if (SortConnectedModelsFirst)
- {
comparer = comparer.ThenByDescending(vm => vm.CheckpointFile.HasConnectedModel);
- }
switch (SelectedSortOption)
{
@@ -301,31 +331,33 @@ or nameof(SortConnectedModelsFirst)
.ObserveOn(SynchronizationContext.Current)
.AsObservable();
- ModelsCache
- .Connect()
- .DeferUntilLoaded()
- .Filter(filterPredicate)
- .Filter(searchPredicate)
- .Transform(
- x =>
- new CheckpointFileViewModel(
- settingsManager,
- modelIndexService,
- notificationService,
- downloadService,
- dialogFactory,
- logger,
- x
- )
- )
- .SortAndBind(Models, comparerObservable)
- .WhenPropertyChanged(p => p.IsSelected)
- .Throttle(TimeSpan.FromMilliseconds(50))
- .ObserveOn(SynchronizationContext.Current)
- .Subscribe(_ =>
- {
- NumItemsSelected = Models.Count(o => o.IsSelected);
- });
+ AddDisposable(
+ ModelsCache
+ .Connect()
+ .DeferUntilLoaded()
+ .Filter(filterPredicate)
+ .Filter(searchPredicate)
+ .Transform(
+ x =>
+ new CheckpointFileViewModel(
+ settingsManager,
+ modelIndexService,
+ notificationService,
+ downloadService,
+ dialogFactory,
+ logger,
+ x
+ )
+ )
+ .SortAndBind(Models, comparerObservable)
+ .WhenPropertyChanged(p => p.IsSelected)
+ .Throttle(TimeSpan.FromMilliseconds(50))
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe(_ =>
+ {
+ NumItemsSelected = Models.Count(o => o.IsSelected);
+ })
+ );
var categoryFilterPredicate = Observable
.FromEventPattern(this, nameof(PropertyChanged))
@@ -336,31 +368,37 @@ or nameof(SortConnectedModelsFirst)
.ObserveOn(SynchronizationContext.Current)
.AsObservable();
- categoriesCache
- .Connect()
- .DeferUntilLoaded()
- .Filter(categoryFilterPredicate)
- .SortAndBind(
- Categories,
- SortExpressionComparer
- .Descending(x => x.Name == "All Models")
- .ThenByAscending(x => x.Name)
- )
- .ObserveOn(SynchronizationContext.Current)
- .Subscribe();
+ AddDisposable(
+ categoriesCache
+ .Connect()
+ .DeferUntilLoaded()
+ .Filter(categoryFilterPredicate)
+ .SortAndBind(
+ Categories,
+ SortExpressionComparer
+ .Descending(x => x.Name == "All Models")
+ .ThenByAscending(x => x.Name)
+ )
+ .ObserveOn(SynchronizationContext.Current)
+ .Subscribe()
+ );
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.IsImportAsConnectedEnabled,
- s => s.IsImportAsConnected,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.IsImportAsConnectedEnabled,
+ s => s.IsImportAsConnected,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.ResizeFactor,
- s => s.CheckpointsPageResizeFactor,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.ResizeFactor,
+ s => s.CheckpointsPageResizeFactor,
+ true
+ )
);
Refresh().SafeFireAndForget();
@@ -374,53 +412,67 @@ or nameof(SortConnectedModelsFirst)
);
};
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.SortConnectedModelsFirst,
- settings => settings.SortConnectedModelsFirst,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.SortConnectedModelsFirst,
+ settings => settings.SortConnectedModelsFirst,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.SelectedSortOption,
- settings => settings.CheckpointSortMode,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.SelectedSortOption,
+ settings => settings.CheckpointSortMode,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.SelectedSortDirection,
- settings => settings.CheckpointSortDirection,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.SelectedSortDirection,
+ settings => settings.CheckpointSortDirection,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.ShowModelsInSubfolders,
- settings => settings.ShowModelsInSubfolders,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.ShowModelsInSubfolders,
+ settings => settings.ShowModelsInSubfolders,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.DragMovesAllSelected,
- settings => settings.DragMovesAllSelected,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.DragMovesAllSelected,
+ settings => settings.DragMovesAllSelected,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.HideEmptyRootCategories,
- settings => settings.HideEmptyRootCategories,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.HideEmptyRootCategories,
+ settings => settings.HideEmptyRootCategories,
+ true
+ )
);
- settingsManager.RelayPropertyFor(
- this,
- vm => vm.ShowNsfwImages,
- settings => settings.ShowNsfwInCheckpointsPage,
- true
+ AddDisposable(
+ settingsManager.RelayPropertyFor(
+ this,
+ vm => vm.ShowNsfwImages,
+ settings => settings.ShowNsfwInCheckpointsPage,
+ true
+ )
);
// make sure a sort happens
@@ -444,9 +496,7 @@ private void ClearSelection()
{
var selected = Models.Where(x => x.IsSelected).ToList();
foreach (var model in selected)
- {
model.IsSelected = false;
- }
NumItemsSelected = 0;
}
@@ -459,9 +509,7 @@ private async Task DeleteAsync()
|| Models.Where(o => o.IsSelected).Select(vm => vm.CheckpointFile).ToList()
is not { Count: > 0 } selectedModelFiles
)
- {
return;
- }
var pathsToDelete = selectedModelFiles
.SelectMany(x => x.GetDeleteFullPaths(settingsManager.ModelsDirectory))
@@ -471,9 +519,7 @@ private async Task DeleteAsync()
confirmDeleteVm.PathsToDelete = pathsToDelete;
if (await confirmDeleteVm.GetDialog().ShowAsync() != ContentDialogResult.Primary)
- {
return;
- }
try
{
@@ -534,17 +580,11 @@ private Task OnItemClick(CheckpointFileViewModel item)
{
// Select item if we're in "select mode"
if (NumItemsSelected > 0)
- {
item.IsSelected = !item.IsSelected;
- }
else if (item.CheckpointFile.HasConnectedModel)
- {
return ShowVersionDialog(item);
- }
else
- {
item.IsSelected = !item.IsSelected;
- }
return Task.CompletedTask;
}
@@ -596,7 +636,7 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item)
IsFooterVisible = false,
CloseOnClickOutside = true,
MaxDialogWidth = 750,
- MaxDialogHeight = 1000,
+ MaxDialogHeight = 1000
};
var htmlDescription = $"""{model.Description}""";
@@ -649,14 +689,9 @@ private async Task ShowVersionDialog(CheckpointFileViewModel item)
private void ClearOrSelectAllBaseModels()
{
if (SelectedBaseModels.Count == BaseModelOptions.Count)
- {
- SelectedBaseModels.Clear();
- }
+ BaseModelOptions.ForEach(x => x.IsSelected = false);
else
- {
- SelectedBaseModels.Clear();
- SelectedBaseModels.AddRange(BaseModelOptions);
- }
+ BaseModelOptions.ForEach(x => x.IsSelected = true);
}
[RelayCommand]
@@ -701,10 +736,12 @@ await notificationService.TryAsync(
}
[RelayCommand]
- private Task OpenFolderFromTreeview(object? treeViewItem) =>
- treeViewItem is CheckpointCategory category && !string.IsNullOrWhiteSpace(category.Path)
+ private Task OpenFolderFromTreeview(object? treeViewItem)
+ {
+ return treeViewItem is CheckpointCategory category && !string.IsNullOrWhiteSpace(category.Path)
? ProcessRunner.OpenFolderBrowser(category.Path)
: Task.CompletedTask;
+ }
[RelayCommand]
private async Task DeleteFolderAsync(object? treeViewItem)
@@ -826,19 +863,13 @@ public async Task MoveBetweenFolders(LocalModelFile sourceFile, DirectoryPath de
// Move files
if (File.Exists(sourcePath))
- {
await FileTransfers.MoveFileAsync(sourcePath, destinationFilePath);
- }
if (File.Exists(sourceCmInfoPath))
- {
await FileTransfers.MoveFileAsync(sourceCmInfoPath, destinationCmInfoPath);
- }
if (File.Exists(sourcePreviewPath))
- {
await FileTransfers.MoveFileAsync(sourcePreviewPath, destinationPreviewPath);
- }
notificationService.Show(
"Model moved successfully",
@@ -892,7 +923,6 @@ private void RefreshCategories()
.ToList();
foreach (var checkpointCategory in modelCategories.SelectMany(c => c.Flatten()))
- {
checkpointCategory.Count = Directory
.EnumerateFileSystemEntries(
checkpointCategory.Path,
@@ -900,13 +930,12 @@ private void RefreshCategories()
EnumerationOptionConstants.AllDirectories
)
.Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(Path.GetExtension(x)));
- }
var rootCategory = new CheckpointCategory
{
Path = settingsManager.ModelsDirectory,
Name = "All Models",
- Count = modelIndexService.ModelIndex.Values.SelectMany(x => x).Count(),
+ Count = modelIndexService.ModelIndex.Values.SelectMany(x => x).Count()
};
categoriesCache.Edit(updater =>
@@ -928,9 +957,7 @@ private void RefreshCategories()
.SelectMany(x => x.Flatten())
.FirstOrDefault(x => x.Path == dirPath.FullPath);
if (category != null)
- {
category.IsExpanded = true;
- }
dirPath = dirPath.Parent;
}
@@ -966,13 +993,11 @@ private ObservableCollection GetSubfolders(string strPath)
Path = dir,
Count = new DirectoryInfo(dir)
.EnumerateFileSystemInfos("*", EnumerationOptionConstants.AllDirectories)
- .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(x.Extension)),
+ .Count(x => LocalModelFile.SupportedCheckpointExtensions.Contains(x.Extension))
};
if (Directory.GetDirectories(dir, "*", EnumerationOptionConstants.TopLevelOnly).Length > 0)
- {
category.SubDirectories = GetSubfolders(dir);
- }
subfolders.Add(category);
}
@@ -983,11 +1008,9 @@ private ObservableCollection GetSubfolders(string strPath)
private string GetConnectedModelInfoFilePath(string filePath)
{
if (string.IsNullOrEmpty(filePath))
- {
throw new InvalidOperationException(
"Cannot get connected model info file path when filePath is empty"
);
- }
var modelNameNoExt = Path.GetFileNameWithoutExtension((string?)filePath);
var modelDir = Path.GetDirectoryName((string?)filePath) ?? "";
@@ -1017,11 +1040,9 @@ private void DelayedClearViewModelProgress(CheckpointFileViewModel viewModel, Ti
private bool FilterModels(LocalModelFile file)
{
if (SelectedCategory?.Path is null || SelectedCategory?.Path == settingsManager.ModelsDirectory)
- {
return file.HasConnectedModel
? SelectedBaseModels.Contains(file.ConnectedModelInfo.BaseModel ?? "Other")
: SelectedBaseModels.Contains("Other");
- }
var folderPath = Path.GetDirectoryName(file.RelativePath);
var categoryRelativePath = SelectedCategory
@@ -1030,9 +1051,7 @@ private bool FilterModels(LocalModelFile file)
.TrimStart(Path.DirectorySeparatorChar);
if (categoryRelativePath == null || folderPath == null)
- {
return false;
- }
if (
(
@@ -1042,9 +1061,7 @@ private bool FilterModels(LocalModelFile file)
)
is false
)
- {
return false;
- }
return ShowModelsInSubfolders
? folderPath.StartsWith(categoryRelativePath)
diff --git a/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
index d8b3937c7..cbe9e130d 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Controls/PaintCanvasViewModel.cs
@@ -215,7 +215,22 @@ public void RenderToSurface(
}
else
{
- layer.Surface.Canvas.Clear(SKColors.Transparent);
+ var currentInfo = layer.Surface.Canvas.DeviceClipBounds;
+ if (currentInfo.Width != CanvasSize.Width || currentInfo.Height != CanvasSize.Height)
+ {
+ // Dispose the old surface
+ layer.Surface.Dispose();
+
+ // Create a brand-new SKSurface with the new size
+ layer.Surface = SKSurface.Create(
+ new SKImageInfo(CanvasSize.Width, CanvasSize.Height)
+ );
+ }
+ else
+ {
+ // No resize needed, just clear
+ layer.Surface.Canvas.Clear(SKColors.Transparent);
+ }
}
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
index 9f31c3500..623d43db8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/NewOneClickInstallViewModel.cs
@@ -87,146 +87,138 @@ INotificationService notificationService
.ThenByAscending(p => p.DisplayName)
)
.ObserveOn(SynchronizationContext.Current)
- .Subscribe();
+ .Subscribe(_ =>
+ {
+ if (ShownPackages.Count > 0)
+ return;
- AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages());
- if (ShownPackages.Count > 0)
- return;
+ ShowIncompatiblePackages = true;
+ });
- ShowIncompatiblePackages = true;
+ AllPackagesCache.AddOrUpdate(packageFactory.GetAllAvailablePackages());
}
[RelayCommand]
- private void InstallComfyForInference()
+ private async Task InstallComfyForInference()
{
var comfyPackage = ShownPackages.FirstOrDefault(x => x is ComfyUI);
if (comfyPackage == null)
return;
isInferenceInstall = true;
- InstallPackage(comfyPackage);
+ await InstallPackage(comfyPackage);
}
[RelayCommand]
- private void InstallPackage(BasePackage selectedPackage)
+ private async Task InstallPackage(BasePackage selectedPackage)
{
- Task.Run(async () =>
- {
- var installLocation = Path.Combine(
- settingsManager.LibraryDir,
- "Packages",
- selectedPackage.Name
- );
-
- var steps = new List
- {
- new SetPackageInstallingStep(settingsManager, selectedPackage.Name),
- new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage),
- };
-
- // get latest version & download & install
- if (Directory.Exists(installLocation))
- {
- var installPath = new DirectoryPath(installLocation);
- await installPath.DeleteVerboseAsync(logger);
- }
-
- var downloadVersion = await selectedPackage.GetLatestVersion();
- var installedVersion = new InstalledPackageVersion { IsPrerelease = false };
+ OnPrimaryButtonClick();
- if (selectedPackage.ShouldIgnoreReleases)
- {
- installedVersion.InstalledBranch = downloadVersion.BranchName;
- installedVersion.InstalledCommitSha = downloadVersion.CommitHash;
- }
- else
- {
- installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag;
- }
+ var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", selectedPackage.Name);
+
+ var steps = new List
+ {
+ new SetPackageInstallingStep(settingsManager, selectedPackage.Name),
+ new SetupPrerequisitesStep(prerequisiteHelper, pyRunner, selectedPackage)
+ };
+
+ // get latest version & download & install
+ if (Directory.Exists(installLocation))
+ {
+ var installPath = new DirectoryPath(installLocation);
+ await installPath.DeleteVerboseAsync(logger);
+ }
+
+ var downloadVersion = await selectedPackage.GetLatestVersion();
+ var installedVersion = new InstalledPackageVersion { IsPrerelease = false };
+
+ if (selectedPackage.ShouldIgnoreReleases)
+ {
+ installedVersion.InstalledBranch = downloadVersion.BranchName;
+ installedVersion.InstalledCommitSha = downloadVersion.CommitHash;
+ }
+ else
+ {
+ installedVersion.InstalledReleaseVersion = downloadVersion.VersionTag;
+ }
+
+ var torchVersion = selectedPackage.GetRecommendedTorchVersion();
+ var recommendedSharedFolderMethod = selectedPackage.RecommendedSharedFolderMethod;
+
+ var installedPackage = new InstalledPackage
+ {
+ DisplayName = selectedPackage.DisplayName,
+ LibraryPath = Path.Combine("Packages", selectedPackage.Name),
+ Id = Guid.NewGuid(),
+ PackageName = selectedPackage.Name,
+ Version = installedVersion,
+ LaunchCommand = selectedPackage.LaunchCommand,
+ LastUpdateCheck = DateTimeOffset.Now,
+ PreferredTorchIndex = torchVersion,
+ PreferredSharedFolderMethod = recommendedSharedFolderMethod
+ };
+
+ var downloadStep = new DownloadPackageVersionStep(
+ selectedPackage,
+ installLocation,
+ new DownloadPackageOptions { VersionOptions = downloadVersion }
+ );
+ steps.Add(downloadStep);
+
+ var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(Path.Combine(installLocation, "venv"));
+ steps.Add(unpackSiteCustomizeStep);
+
+ var installStep = new InstallPackageStep(
+ selectedPackage,
+ installLocation,
+ installedPackage,
+ new InstallPackageOptions
+ {
+ SharedFolderMethod = recommendedSharedFolderMethod,
+ VersionOptions = downloadVersion,
+ PythonOptions = { TorchIndex = torchVersion }
+ }
+ );
+ steps.Add(installStep);
+
+ var setupModelFoldersStep = new SetupModelFoldersStep(
+ selectedPackage,
+ recommendedSharedFolderMethod,
+ installLocation
+ );
+ steps.Add(setupModelFoldersStep);
+
+ var setupOutputSharingStep = new SetupOutputSharingStep(selectedPackage, installLocation);
+ steps.Add(setupOutputSharingStep);
+
+ var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage);
+ steps.Add(addInstalledPackageStep);
+
+ var runner = new PackageModificationRunner
+ {
+ ShowDialogOnStart = false,
+ HideCloseButton = false,
+ ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully"
+ };
+
+ runner
+ .ExecuteSteps(steps)
+ .ContinueWith(_ =>
+ {
+ notificationService.OnPackageInstallCompleted(runner);
- var torchVersion = selectedPackage.GetRecommendedTorchVersion();
- var recommendedSharedFolderMethod = selectedPackage.RecommendedSharedFolderMethod;
+ EventManager.Instance.OnOneClickInstallFinished(false);
- var installedPackage = new InstalledPackage
- {
- DisplayName = selectedPackage.DisplayName,
- LibraryPath = Path.Combine("Packages", selectedPackage.Name),
- Id = Guid.NewGuid(),
- PackageName = selectedPackage.Name,
- Version = installedVersion,
- LaunchCommand = selectedPackage.LaunchCommand,
- LastUpdateCheck = DateTimeOffset.Now,
- PreferredTorchIndex = torchVersion,
- PreferredSharedFolderMethod = recommendedSharedFolderMethod
- };
-
- var downloadStep = new DownloadPackageVersionStep(
- selectedPackage,
- installLocation,
- new DownloadPackageOptions { VersionOptions = downloadVersion }
- );
- steps.Add(downloadStep);
-
- var unpackSiteCustomizeStep = new UnpackSiteCustomizeStep(
- Path.Combine(installLocation, "venv")
- );
- steps.Add(unpackSiteCustomizeStep);
-
- var installStep = new InstallPackageStep(
- selectedPackage,
- installLocation,
- installedPackage,
- new InstallPackageOptions
- {
- SharedFolderMethod = recommendedSharedFolderMethod,
- VersionOptions = downloadVersion,
- PythonOptions = { TorchIndex = torchVersion }
- }
- );
- steps.Add(installStep);
-
- var setupModelFoldersStep = new SetupModelFoldersStep(
- selectedPackage,
- recommendedSharedFolderMethod,
- installLocation
- );
- steps.Add(setupModelFoldersStep);
-
- var addInstalledPackageStep = new AddInstalledPackageStep(settingsManager, installedPackage);
- steps.Add(addInstalledPackageStep);
+ if (!isInferenceInstall)
+ return;
Dispatcher.UIThread.Post(() =>
{
- var runner = new PackageModificationRunner
- {
- ShowDialogOnStart = false,
- HideCloseButton = false,
- ModificationCompleteMessage = $"{selectedPackage.DisplayName} installed successfully"
- };
-
- runner
- .ExecuteSteps(steps)
- .ContinueWith(_ =>
- {
- notificationService.OnPackageInstallCompleted(runner);
-
- EventManager.Instance.OnOneClickInstallFinished(false);
-
- if (!isInferenceInstall)
- return;
-
- Dispatcher.UIThread.Post(() =>
- {
- navigationService.NavigateTo();
- });
- })
- .SafeFireAndForget();
-
- EventManager.Instance.OnPackageInstallProgressAdded(runner);
+ navigationService.NavigateTo();
});
})
.SafeFireAndForget();
- OnPrimaryButtonClick();
+ EventManager.Instance.OnPackageInstallProgressAdded(runner);
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
index a68a82812..05f208152 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/RecommendedModelsViewModel.cs
@@ -115,7 +115,7 @@ public override async Task OnLoadedAsync()
)
);
}
- catch (ApiException e)
+ catch (Exception e)
{
// hide dialog and show error msg
logger.LogError(e, "Failed to get recommended models");
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
index 04189239a..8852a5e51 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageGalleryCardViewModel.cs
@@ -88,7 +88,7 @@ public void SetPreviewImage(byte[] imageBytes)
PreviewImage = bitmap;
IsPreviewOverlayEnabled = true;
- currentImage?.Dispose();
+ // currentImage?.Dispose();
});
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
index 5e7233f29..b77fdbfd6 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Inference/SelectImageCardViewModel.cs
@@ -7,6 +7,7 @@
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
+using Avalonia.Controls.Notifications;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
@@ -206,6 +207,11 @@ private async Task OpenEditMaskDialogAsync()
if (await ImageSource.GetBitmapAsync() is not { } currentBitmap)
{
Logger.Warn("GetBitmapAsync returned null for image {Path}", ImageSource.LocalFile?.FullPath);
+ notificationService.ShowPersistent(
+ "Error Loading Image",
+ "Could not load mask editor for the provided image.",
+ NotificationType.Error
+ );
return;
}
MaskEditorViewModel.PaintCanvasViewModel.BackgroundImage = currentBitmap.ToSKBitmap();
@@ -289,6 +295,6 @@ private void LoadUserImage(ImageSource image)
ImageSource = image;
- current?.Dispose();
+ // current?.Dispose();
}
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
index 67b98c1d6..c701142bf 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
@@ -114,7 +114,7 @@ RunningPackageService runningPackageService
private void OnOneClickInstallFinished(object? sender, bool e)
{
- OnLoadedAsync().SafeFireAndForget();
+ Dispatcher.UIThread.Post(() => OnLoadedAsync().SafeFireAndForget());
}
public void SetPackages(IEnumerable packages)
diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
index 709ffaf36..82b60b948 100644
--- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
+++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
@@ -34,6 +34,7 @@
False
True
+
@@ -288,28 +289,27 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
diff --git a/StabilityMatrix.Core/Database/ILiteDbContext.cs b/StabilityMatrix.Core/Database/ILiteDbContext.cs
index 0b9e6150c..21d3984d1 100644
--- a/StabilityMatrix.Core/Database/ILiteDbContext.cs
+++ b/StabilityMatrix.Core/Database/ILiteDbContext.cs
@@ -14,6 +14,7 @@ public interface ILiteDbContext : IDisposable
ILiteCollectionAsync LocalModelFiles { get; }
ILiteCollectionAsync InferenceProjects { get; }
ILiteCollectionAsync LocalImageFiles { get; }
+ ILiteCollectionAsync CivitBaseModelTypeCache { get; }
Task<(CivitModel?, CivitModelVersion?)> FindCivitModelFromFileHashAsync(string hashBlake3);
Task UpsertCivitModelAsync(CivitModel civitModel);
@@ -44,4 +45,7 @@ Task task
Task GetPyPiCacheEntry(string? cacheKey);
Task UpsertPyPiCacheEntry(PyPiCacheEntry cacheEntry);
+
+ Task GetCivitBaseModelTypeCacheEntry(string id);
+ Task UpsertCivitBaseModelTypeCacheEntry(CivitBaseModelTypeCacheEntry entry);
}
diff --git a/StabilityMatrix.Core/Database/LiteDbContext.cs b/StabilityMatrix.Core/Database/LiteDbContext.cs
index 5e318f4d4..7f1ee8ec0 100644
--- a/StabilityMatrix.Core/Database/LiteDbContext.cs
+++ b/StabilityMatrix.Core/Database/LiteDbContext.cs
@@ -42,6 +42,8 @@ public class LiteDbContext : ILiteDbContext
Database.GetCollection("LocalImageFiles");
public ILiteCollectionAsync PyPiCache =>
Database.GetCollection("PyPiCache");
+ public ILiteCollectionAsync CivitBaseModelTypeCache =>
+ Database.GetCollection("CivitBaseModelTypeCache");
public LiteDbContext(
ILogger logger,
@@ -384,6 +386,17 @@ var subCollectionName in FindReferencedCollectionNamesRecursive(
}
}
+ public async Task GetCivitBaseModelTypeCacheEntry(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ return null;
+
+ return await CivitBaseModelTypeCache.FindByIdAsync(id).ConfigureAwait(false);
+ }
+
+ public Task UpsertCivitBaseModelTypeCacheEntry(CivitBaseModelTypeCacheEntry entry) =>
+ CivitBaseModelTypeCache.UpsertAsync(entry);
+
private readonly record struct HandledExceptionInfo(
string CollectionName,
string Exception,
diff --git a/StabilityMatrix.Core/Models/Database/CivitBaseModelTypeCacheEntry.cs b/StabilityMatrix.Core/Models/Database/CivitBaseModelTypeCacheEntry.cs
new file mode 100644
index 000000000..a335318bc
--- /dev/null
+++ b/StabilityMatrix.Core/Models/Database/CivitBaseModelTypeCacheEntry.cs
@@ -0,0 +1,8 @@
+namespace StabilityMatrix.Core.Models.Database;
+
+public class CivitBaseModelTypeCacheEntry
+{
+ public required string Id { get; set; }
+ public List ModelTypes { get; set; } = [];
+ public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
+}
diff --git a/StabilityMatrix.Core/Models/Settings/Settings.cs b/StabilityMatrix.Core/Models/Settings/Settings.cs
index b35c1638f..5a9ad023d 100644
--- a/StabilityMatrix.Core/Models/Settings/Settings.cs
+++ b/StabilityMatrix.Core/Models/Settings/Settings.cs
@@ -169,11 +169,7 @@ public IReadOnlyDictionary EnvironmentVariables
public Dictionary NotificationOptions { get; set; } = new();
- public List SelectedBaseModels { get; set; } =
- Enum.GetValues()
- .Where(x => x != CivitBaseModelType.All)
- .Select(x => x.GetStringValue())
- .ToList();
+ public List SelectedBaseModels { get; set; } = [];
public Size InferenceImageSize { get; set; } = new(150, 190);