Skip to content

Commit ce9cf5f

Browse files
committed
Added prev/next model buttons on details page, manual install button for package extensions, brought back old size remaining tooltip, and fix extension clone
1 parent a7d1139 commit ce9cf5f

11 files changed

Lines changed: 299 additions & 176 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
77

88
## v2.15.0-pre.2
9+
### Added
10+
- Added Manual Install button for installing Package extensions that aren't in the indexes
11+
- Added Next and Previous buttons to the Civitai details page to navigate between results
12+
### Changed
13+
- Brought back the "size remaining after download" tooltip in the new Civitai details page
914
### Fixed
1015
- Fixed Inference custom step (e.g. HiresFix) Samplers potentially sharing state with other card UIs like model browser.
16+
- Fixed extension manager failing to install extensions due to incorrect clone directory
1117

1218
## v2.15.0-pre.1
1319
### Added

StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs

Lines changed: 1 addition & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
1-
using System;
2-
using System.Collections.Immutable;
3-
using System.IO;
4-
using System.Linq;
5-
using System.Text.RegularExpressions;
6-
using System.Threading.Tasks;
7-
using AsyncAwaitBestPractices;
1+
using System.Text.RegularExpressions;
82
using Avalonia.Controls;
93
using Avalonia.Controls.Notifications;
104
using Avalonia.Threading;
115
using CommunityToolkit.Mvvm.ComponentModel;
126
using CommunityToolkit.Mvvm.Input;
13-
using FluentAvalonia.UI.Controls;
147
using Injectio.Attributes;
158
using NLog;
169
using StabilityMatrix.Avalonia.Animations;
17-
using StabilityMatrix.Avalonia.Controls;
1810
using StabilityMatrix.Avalonia.Services;
1911
using StabilityMatrix.Avalonia.ViewModels.Base;
20-
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
21-
using StabilityMatrix.Avalonia.Views.Dialogs;
2212
using StabilityMatrix.Core.Api;
2313
using StabilityMatrix.Core.Attributes;
2414
using StabilityMatrix.Core.Database;
25-
using StabilityMatrix.Core.Extensions;
2615
using StabilityMatrix.Core.Helper;
27-
using StabilityMatrix.Core.Models;
2816
using StabilityMatrix.Core.Models.Api;
2917
using StabilityMatrix.Core.Models.Database;
3018
using StabilityMatrix.Core.Models.FileInterfaces;
@@ -227,162 +215,6 @@ public void SearchAuthor()
227215
EventManager.Instance.OnNavigateAndFindCivitAuthorRequested(CivitModel.Creator.Username);
228216
}
229217

230-
[RelayCommand]
231-
private async Task ShowVersionDialog(CivitModel model)
232-
{
233-
var versions = model.ModelVersions;
234-
if (versions is null || versions.Count == 0)
235-
{
236-
notificationService.Show(
237-
new Notification(
238-
"Model has no versions available",
239-
"This model has no versions available for download",
240-
NotificationType.Warning
241-
)
242-
);
243-
return;
244-
}
245-
246-
var newVm = dialogFactory.Get<CivitDetailsPageViewModel>(vm =>
247-
{
248-
vm.CivitModel = model;
249-
return vm;
250-
});
251-
252-
navigationService.NavigateTo(newVm, BetterSlideNavigationTransition.PageSlideFromRight);
253-
return;
254-
255-
var dialog = new BetterContentDialog
256-
{
257-
Title = model.Name,
258-
IsPrimaryButtonEnabled = false,
259-
IsSecondaryButtonEnabled = false,
260-
IsFooterVisible = false,
261-
CloseOnClickOutside = true,
262-
MaxDialogWidth = 750,
263-
MaxDialogHeight = 1000,
264-
};
265-
266-
var htmlDescription = $"""<html><body class="markdown-body">{model.Description}</body></html>""";
267-
268-
var viewModel = dialogFactory.Get<SelectModelVersionViewModel>();
269-
viewModel.Dialog = dialog;
270-
viewModel.Title = model.Name;
271-
272-
viewModel.Description = htmlDescription;
273-
viewModel.CivitModel = model;
274-
viewModel.Versions = versions
275-
.Where(v => !settingsManager.Settings.HideEarlyAccessModels || !v.IsEarlyAccess)
276-
.Select(version => new ModelVersionViewModel(modelIndexService, version))
277-
.ToImmutableArray();
278-
viewModel.SelectedVersionViewModel = viewModel.Versions.Any() ? viewModel.Versions[0] : null;
279-
280-
// Update with latest version (including files) if we have no files
281-
if (model.ModelVersions?.FirstOrDefault()?.Files is not { Count: > 0 })
282-
{
283-
Task.Run(async () =>
284-
{
285-
Logger.Debug("No files found for model {ModelId}. Updating versions...", model.Id);
286-
287-
var latestModel = await civitApi.GetModelById(model.Id);
288-
var latestVersions = latestModel.ModelVersions ?? [];
289-
290-
// Update our model
291-
civitModel.Description = latestModel.Description;
292-
civitModel = latestModel;
293-
foreach (var version in latestVersions)
294-
{
295-
if (version.Files is not { Count: > 0 })
296-
continue;
297-
298-
var targetVersion = model.ModelVersions?.FirstOrDefault(v => v.Id == version.Id);
299-
if (targetVersion is null)
300-
continue;
301-
302-
targetVersion.Files = version.Files;
303-
targetVersion.Description = version.Description;
304-
targetVersion.DownloadUrl = version.DownloadUrl;
305-
}
306-
307-
// Reinitialize
308-
Logger.Debug("Updating Versions dialog");
309-
Dispatcher.UIThread.Post(() =>
310-
{
311-
var newHtmlDescription =
312-
$"""<html><body class="markdown-body">{model.Description}</body></html>""";
313-
314-
viewModel.Dialog = dialog;
315-
viewModel.Title = latestModel.Name;
316-
317-
viewModel.Description = newHtmlDescription;
318-
viewModel.CivitModel = latestModel;
319-
viewModel.Versions = (latestModel.ModelVersions ?? [])
320-
.Where(v => !settingsManager.Settings.HideEarlyAccessModels || !v.IsEarlyAccess)
321-
.Select(version => new ModelVersionViewModel(modelIndexService, version))
322-
.ToImmutableArray();
323-
viewModel.SelectedVersionViewModel = viewModel.Versions.Any()
324-
? viewModel.Versions[0]
325-
: null;
326-
});
327-
328-
// Save to db
329-
var upsertResult = await liteDbContext.UpsertCivitModelAsync(latestModel);
330-
Logger.Debug(
331-
"Update model {ModelId} with latest version: {Result}",
332-
model.Id,
333-
upsertResult
334-
);
335-
})
336-
.SafeFireAndForget(e => Logger.Error(e, "Failed to update model {ModelId}", model.Id));
337-
}
338-
339-
dialog.Content = new SelectModelVersionDialog { DataContext = viewModel };
340-
341-
var result = await dialog.ShowAsync();
342-
343-
if (result != ContentDialogResult.Primary)
344-
{
345-
return;
346-
}
347-
348-
var selectedVersion = viewModel?.SelectedVersionViewModel?.ModelVersion;
349-
var selectedFile = viewModel?.SelectedFile?.CivitFile;
350-
351-
DirectoryPath downloadPath;
352-
if (viewModel?.IsCustomSelected is true)
353-
{
354-
downloadPath = viewModel.CustomInstallLocation;
355-
}
356-
else
357-
{
358-
var sharedFolder = model.Type.ConvertTo<SharedFolderType>().GetStringValue();
359-
360-
if (
361-
model.BaseModelType == CivitBaseModelType.Flux1D.GetStringValue()
362-
|| model.BaseModelType == CivitBaseModelType.Flux1S.GetStringValue()
363-
|| model.BaseModelType == CivitBaseModelType.WanVideo.GetStringValue()
364-
|| model.BaseModelType == CivitBaseModelType.HunyuanVideo.GetStringValue()
365-
|| selectedFile?.Metadata.Format is CivitModelFormat.GGUF
366-
)
367-
{
368-
sharedFolder = SharedFolderType.DiffusionModels.GetStringValue();
369-
}
370-
371-
var defaultPath = Path.Combine(@"Models", sharedFolder);
372-
373-
var subFolder = viewModel?.SelectedInstallLocation ?? defaultPath;
374-
subFolder = subFolder.StripStart(@$"Models{Path.DirectorySeparatorChar}");
375-
downloadPath = Path.Combine(settingsManager.ModelsDirectory, subFolder);
376-
}
377-
378-
await Task.Delay(100);
379-
await DoImport(model, downloadPath, selectedVersion, selectedFile);
380-
381-
Text = "Import started. Check the downloads tab for progress.";
382-
Value = 100;
383-
DelayedClearProgress(TimeSpan.FromMilliseconds(1000));
384-
}
385-
386218
private async Task DoImport(
387219
CivitModel model,
388220
DirectoryPath downloadFolder,

StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Injectio.Attributes;
1515
using NLog;
1616
using Refit;
17+
using StabilityMatrix.Avalonia.Animations;
1718
using StabilityMatrix.Avalonia.Languages;
1819
using StabilityMatrix.Avalonia.Models;
1920
using StabilityMatrix.Avalonia.Services;
@@ -41,10 +42,12 @@ public sealed partial class CivitAiBrowserViewModel : TabViewModelBase, IInfinit
4142
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
4243
private readonly CivitCompatApiManager civitApi;
4344
private readonly ISettingsManager settingsManager;
45+
private readonly IServiceManager<ViewModelBase> dialogFactory;
4446
private readonly ILiteDbContext liteDbContext;
4547
private readonly IConnectedServiceManager connectedServiceManager;
4648
private readonly INotificationService notificationService;
4749
private readonly ICivitBaseModelTypeService baseModelTypeService;
50+
private readonly INavigationService<MainWindowViewModel> navigationService;
4851
private bool dontSearch = false;
4952

5053
private readonly SourceCache<OrderedValue<CivitModel>, int> modelCache = new(static ov => ov.Value.Id);
@@ -141,15 +144,18 @@ public CivitAiBrowserViewModel(
141144
ILiteDbContext liteDbContext,
142145
IConnectedServiceManager connectedServiceManager,
143146
INotificationService notificationService,
144-
ICivitBaseModelTypeService baseModelTypeService
147+
ICivitBaseModelTypeService baseModelTypeService,
148+
INavigationService<MainWindowViewModel> navigationService
145149
)
146150
{
147151
this.civitApi = civitApi;
148152
this.settingsManager = settingsManager;
153+
this.dialogFactory = dialogFactory;
149154
this.liteDbContext = liteDbContext;
150155
this.connectedServiceManager = connectedServiceManager;
151156
this.notificationService = notificationService;
152157
this.baseModelTypeService = baseModelTypeService;
158+
this.navigationService = navigationService;
153159

154160
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
155161

@@ -792,6 +798,39 @@ private void ClearOrSelectAllBaseModels()
792798
AllBaseModels.ForEach(x => x.IsSelected = true);
793799
}
794800

801+
[RelayCommand]
802+
private void ShowVersionDialog(CivitModel model)
803+
{
804+
var versions = model.ModelVersions;
805+
if (versions is null || versions.Count == 0)
806+
{
807+
notificationService.Show(
808+
new Notification(
809+
"Model has no versions available",
810+
"This model has no versions available for download",
811+
NotificationType.Warning
812+
)
813+
);
814+
return;
815+
}
816+
817+
var newVm = dialogFactory.Get<CivitDetailsPageViewModel>(vm =>
818+
{
819+
var allModelIds = ModelCards.Select(x => x.CivitModel.Id).Distinct().ToList();
820+
var index = ModelCards
821+
.Select((x, i) => (x.CivitModel.Id, Index: i))
822+
.FirstOrDefault(x => x.Id == model.Id)
823+
.Index;
824+
825+
vm.ModelIdList = allModelIds;
826+
vm.CurrentIndex = index;
827+
vm.CivitModel = model;
828+
return vm;
829+
});
830+
831+
navigationService.NavigateTo(newVm, BetterSlideNavigationTransition.PageSlideFromRight);
832+
}
833+
795834
public void ClearSearchQuery()
796835
{
797836
SearchQuery = string.Empty;

StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitDetailsPageViewModel.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ IModelImportService modelImportService
5858
[NotifyPropertyChangedFor(nameof(ShowInferenceDefaultsSection))]
5959
public required partial CivitModel CivitModel { get; set; }
6060

61+
[ObservableProperty]
62+
public required partial List<int> ModelIdList { get; set; }
63+
64+
[ObservableProperty]
65+
[NotifyPropertyChangedFor(nameof(CanGoNext), nameof(CanGoPrevious))]
66+
public required partial int CurrentIndex { get; set; }
67+
6168
private List<string> ignoredFileNameFormatVars =
6269
[
6370
"seed",
@@ -168,6 +175,9 @@ IModelImportService modelImportService
168175

169176
public bool ShowInferenceDefaultsSection => CivitModel.Type == CivitModelType.Checkpoint;
170177

178+
public bool CanGoNext => CurrentIndex < ModelIdList.Count - 1;
179+
public bool CanGoPrevious => CurrentIndex > 0;
180+
171181
protected override async Task OnInitialLoadedAsync()
172182
{
173183
if (
@@ -720,6 +730,66 @@ private async Task DeleteModelVersion(CivitModelVersion modelVersion)
720730
}
721731
}
722732

733+
[RelayCommand]
734+
public async Task NextModel()
735+
{
736+
if (!CanGoNext)
737+
return;
738+
739+
try
740+
{
741+
var modelId = ModelIdList[++CurrentIndex];
742+
CivitModel = await civitApi.GetModelById(modelId);
743+
ReloadCachesForNewModel();
744+
}
745+
catch (Exception e)
746+
{
747+
logger.LogError(e, "Failed to load CivitModel {Id}", CivitModel.Id);
748+
notificationService.Show(
749+
Resources.Label_UnexpectedErrorOccurred,
750+
e.Message,
751+
NotificationType.Error
752+
);
753+
}
754+
}
755+
756+
[RelayCommand]
757+
public async Task PreviousModel()
758+
{
759+
if (!CanGoPrevious)
760+
return;
761+
762+
try
763+
{
764+
var modelId = ModelIdList[--CurrentIndex];
765+
CivitModel = await civitApi.GetModelById(modelId);
766+
ReloadCachesForNewModel();
767+
}
768+
catch (Exception e)
769+
{
770+
logger.LogError(e, "Failed to load CivitModel {Id}", CivitModel.Id);
771+
notificationService.Show(
772+
Resources.Label_UnexpectedErrorOccurred,
773+
e.Message,
774+
NotificationType.Error
775+
);
776+
}
777+
}
778+
779+
private void ReloadCachesForNewModel()
780+
{
781+
modelVersionCache.EditDiff(CivitModel.ModelVersions ?? [], (a, b) => a.Id == b.Id);
782+
SelectedVersion = ModelVersions.FirstOrDefault();
783+
784+
imageCache.EditDiff(SelectedVersion?.ModelVersion.Images ?? [], (a, b) => a.Url == b.Url);
785+
civitFileCache.EditDiff(SelectedVersion?.ModelVersion.Files ?? [], (a, b) => a.Id == b.Id);
786+
787+
Description = $"""<html><body class="markdown-body">{CivitModel.Description}</body></html>""";
788+
ModelVersionDescription = string.IsNullOrWhiteSpace(SelectedVersion?.ModelVersion.Description)
789+
? string.Empty
790+
: $"""<html><body class="markdown-body">{SelectedVersion.ModelVersion.Description}</body></html>""";
791+
}
792+
723793
private void VmOnNavigateToModelRequested(object? sender, int modelId)
724794
{
725795
if (sender is not ImageViewerViewModel vm)

0 commit comments

Comments
 (0)