@ -8,6 +8,7 @@ using System.Threading.Tasks;
using AsyncAwaitBestPractices ;
using AsyncAwaitBestPractices ;
using Avalonia ;
using Avalonia ;
using Avalonia.Controls ;
using Avalonia.Controls ;
using Avalonia.Controls.Notifications ;
using Avalonia.Media.Imaging ;
using Avalonia.Media.Imaging ;
using Avalonia.Platform ;
using Avalonia.Platform ;
using Avalonia.Threading ;
using Avalonia.Threading ;
@ -15,6 +16,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input ;
using CommunityToolkit.Mvvm.Input ;
using FluentAvalonia.UI.Controls ;
using FluentAvalonia.UI.Controls ;
using NLog ;
using NLog ;
using Octokit ;
using StabilityMatrix.Avalonia.Services ;
using StabilityMatrix.Avalonia.Services ;
using StabilityMatrix.Avalonia.Views.Dialogs ;
using StabilityMatrix.Avalonia.Views.Dialogs ;
using StabilityMatrix.Core.Extensions ;
using StabilityMatrix.Core.Extensions ;
@ -25,6 +27,7 @@ using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress ;
using StabilityMatrix.Core.Models.Progress ;
using StabilityMatrix.Core.Processes ;
using StabilityMatrix.Core.Processes ;
using StabilityMatrix.Core.Services ;
using StabilityMatrix.Core.Services ;
using Notification = Avalonia . Controls . Notifications . Notification ;
namespace StabilityMatrix.Avalonia.ViewModels ;
namespace StabilityMatrix.Avalonia.ViewModels ;
@ -35,6 +38,7 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
private readonly IDownloadService downloadService ;
private readonly IDownloadService downloadService ;
private readonly ISettingsManager settingsManager ;
private readonly ISettingsManager settingsManager ;
private readonly IDialogFactory dialogFactory ;
private readonly IDialogFactory dialogFactory ;
private readonly INotificationService notificationService ;
public CivitModel CivitModel { get ; init ; }
public CivitModel CivitModel { get ; init ; }
public Bitmap ? CardImage { get ; set ; }
public Bitmap ? CardImage { get ; set ; }
public override bool IsTextVisible = > Value > 0 ;
public override bool IsTextVisible = > Value > 0 ;
@ -42,15 +46,17 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
[ObservableProperty] private bool isImporting ;
[ObservableProperty] private bool isImporting ;
public CheckpointBrowserCardViewModel (
public CheckpointBrowserCardViewModel (
CivitModel civitModel ,
CivitModel civitModel ,
IDownloadService downloadService ,
IDownloadService downloadService ,
ISettingsManager settingsManager ,
ISettingsManager settingsManager ,
IDialogFactory dialogFactory ,
IDialogFactory dialogFactory ,
INotificationService notificationService ,
Bitmap ? fixedImage = null )
Bitmap ? fixedImage = null )
{
{
this . downloadService = downloadService ;
this . downloadService = downloadService ;
this . settingsManager = settingsManager ;
this . settingsManager = settingsManager ;
this . dialogFactory = dialogFactory ;
this . dialogFactory = dialogFactory ;
this . notificationService = notificationService ;
CivitModel = civitModel ;
CivitModel = civitModel ;
if ( fixedImage ! = null )
if ( fixedImage ! = null )
@ -60,13 +66,13 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
}
}
UpdateImage ( ) . SafeFireAndForget ( ) ;
UpdateImage ( ) . SafeFireAndForget ( ) ;
// Update image when nsfw setting changes
// Update image when nsfw setting changes
settingsManager . RegisterPropertyChangedHandler (
settingsManager . RegisterPropertyChangedHandler (
s = > s . ModelBrowserNsfwEnabled ,
s = > s . ModelBrowserNsfwEnabled ,
_ = > UpdateImage ( ) . SafeFireAndForget ( ) ) ;
_ = > UpdateImage ( ) . SafeFireAndForget ( ) ) ;
}
}
// Choose and load image based on nsfw setting
// Choose and load image based on nsfw setting
private async Task UpdateImage ( )
private async Task UpdateImage ( )
{
{
@ -78,20 +84,15 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
if ( image ! = null )
if ( image ! = null )
{
{
var imageStream = await downloadService . GetImageStreamFromUrl ( image . Url ) ;
var imageStream = await downloadService . GetImageStreamFromUrl ( image . Url ) ;
Dispatcher . UIThread . Invoke ( ( ) = >
Dispatcher . UIThread . Invoke ( ( ) = > { CardImage = new Bitmap ( imageStream ) ; } ) ;
{
CardImage = new Bitmap ( imageStream ) ;
} ) ;
return ;
return ;
}
}
var assetStream = AssetLoader . Open ( new Uri ( "avares://StabilityMatrix.Avalonia/Assets/noimage.png" ) ) ;
var assetStream =
AssetLoader . Open ( new Uri ( "avares://StabilityMatrix.Avalonia/Assets/noimage.png" ) ) ;
// Otherwise Default image
// Otherwise Default image
Dispatcher . UIThread . Invoke ( ( ) = >
Dispatcher . UIThread . Invoke ( ( ) = > { CardImage = new Bitmap ( assetStream ) ; } ) ;
{
CardImage = new Bitmap ( assetStream ) ;
} ) ;
}
}
// On any mode changes, update the image
// On any mode changes, update the image
@ -131,22 +132,23 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
{
{
return ;
return ;
}
}
var selectedVersion = viewModel ? . SelectedVersion ;
var selectedVersion = viewModel ? . SelectedVersion ;
var selectedFile = viewModel ? . SelectedFile ;
var selectedFile = viewModel ? . SelectedFile ;
await Task . Delay ( 1 0 0 ) ;
await Task . Delay ( 1 0 0 ) ;
await DoImport ( model , selectedVersion , selectedFile ) ;
await DoImport ( model , selectedVersion , selectedFile ) ;
}
}
private async Task DoImport ( CivitModel model , CivitModelVersion ? selectedVersion = null , CivitFile ? selectedFile = null )
private async Task DoImport ( CivitModel model , CivitModelVersion ? selectedVersion = null ,
CivitFile ? selectedFile = null )
{
{
IsImporting = true ;
IsImporting = true ;
Text = "Downloading..." ;
Text = "Downloading..." ;
// Holds files to be deleted on errors
// Holds files to be deleted on errors
var filesForCleanup = new HashSet < FilePath > ( ) ;
var filesForCleanup = new HashSet < FilePath > ( ) ;
// Set Text when exiting, finally block will set 100 and delay clear progress
// Set Text when exiting, finally block will set 100 and delay clear progress
try
try
{
{
@ -154,9 +156,8 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
var modelVersion = selectedVersion ? ? model . ModelVersions ? . FirstOrDefault ( ) ;
var modelVersion = selectedVersion ? ? model . ModelVersions ? . FirstOrDefault ( ) ;
if ( modelVersion is null )
if ( modelVersion is null )
{
{
// snackbarService.ShowSnackbarAsync(
notificationService . Show ( new Notification ( "Model has no versions available" ,
// "This model has no versions available for download",
"This model has no versions available for download" , NotificationType . Warning ) ) ;
// "Model has no versions available", ControlAppearance.Caution).SafeFireAndForget();
Text = "Unable to Download" ;
Text = "Unable to Download" ;
return ;
return ;
}
}
@ -166,9 +167,8 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
modelVersion . Files ? . FirstOrDefault ( x = > x . Type = = CivitFileType . Model ) ;
modelVersion . Files ? . FirstOrDefault ( x = > x . Type = = CivitFileType . Model ) ;
if ( modelFile is null )
if ( modelFile is null )
{
{
// snackbarService.ShowSnackbarAsync(
notificationService . Show ( new Notification ( "Model has no files available" ,
// "This model has no files available for download",
"This model has no files available for download" , NotificationType . Warning ) ) ;
// "Model has no files available", ControlAppearance.Caution).SafeFireAndForget();
Text = "Unable to Download" ;
Text = "Unable to Download" ;
return ;
return ;
}
}
@ -192,24 +192,23 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
} ) ;
} ) ;
} ) ) ;
} ) ) ;
await downloadTask ;
var downloadResult =
await notificationService . TryAsync ( downloadTask , "Could not download file" ) ;
// var downloadResult = await snackbarService.TryAsync(downloadTask, "Could not download file");
// Failed download handling
// Failed download handling
// if (downloadResult.Exception is not null )
if ( downloadResult . Exception is not null )
// {
{
// // For exceptions other than ApiException or TaskCanceledException, log error
// For exceptions other than ApiException or TaskCanceledException, log error
// var logLevel = downloadResult.Exception switch
var logLevel = downloadResult . Exception switch
// {
{
// HttpRequestException or ApiException or TaskCanceledException => LogLevel.Warn ,
HttpRequestException or ApiException or TaskCanceledException = > LogLevel . Warn ,
// _ => LogLevel. Error
_ = > LogLevel . Error
// } ;
} ;
// Logger.Log(logLevel, downloadResult.Exception, "Error during model download") ;
Logger . Log ( logLevel , downloadResult . Exception , "Error during model download" ) ;
//
// Text = "Download Failed" ;
Text = "Download Failed" ;
// return ;
return ;
// }
}
// When sha256 is available, validate the downloaded file
// When sha256 is available, validate the downloaded file
var fileExpectedSha256 = modelFile . Hashes . SHA256 ;
var fileExpectedSha256 = modelFile . Hashes . SHA256 ;
@ -225,14 +224,15 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
{
{
Text = "Import Failed!" ;
Text = "Import Failed!" ;
DelayedClearProgress ( TimeSpan . FromMilliseconds ( 8 0 0 ) ) ;
DelayedClearProgress ( TimeSpan . FromMilliseconds ( 8 0 0 ) ) ;
// snackbarService.ShowSnackbarAsync(
notificationService . Show ( new Notification ( "Download failed hash validation" ,
// "This may be caused by network or server issues from CivitAI, please try again in a few minutes.",
"This may be caused by network or server issues from CivitAI, please try again in a few minutes." ,
// "Download failed hash validation").SafeFireAndForget() ;
NotificationType . Error ) ) ;
Text = "Download Failed" ;
Text = "Download Failed" ;
return ;
return ;
}
}
// snackbarService.ShowSnackbarAsync($"{model.Type} {model.Name} imported successfully!",
// "Import complete", ControlAppearance.Info).SafeFireAndForget();
notificationService . Show ( new Notification ( "Import complete" ,
$"{model.Type} {model.Name} imported successfully!" , NotificationType . Success ) ) ;
}
}
IsIndeterminate = true ;
IsIndeterminate = true ;
@ -258,7 +258,7 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
filesForCleanup . Add ( imageDownloadPath ) ;
filesForCleanup . Add ( imageDownloadPath ) ;
var imageTask =
var imageTask =
downloadService . DownloadToFileAsync ( image . Url , imageDownloadPath ) ;
downloadService . DownloadToFileAsync ( image . Url , imageDownloadPath ) ;
// await snackbarService.TryAsync(imageTask, "Could not download preview image") ;
await notificationService . TryAsync ( imageTask , "Could not download preview image" ) ;
}
}
}
}
@ -278,12 +278,13 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
file . Delete ( ) ;
file . Delete ( ) ;
Logger . Info ( $"Download cleanup: Deleted file {file}" ) ;
Logger . Info ( $"Download cleanup: Deleted file {file}" ) ;
}
}
IsIndeterminate = false ;
IsIndeterminate = false ;
Value = 1 0 0 ;
Value = 1 0 0 ;
DelayedClearProgress ( TimeSpan . FromMilliseconds ( 8 0 0 ) ) ;
DelayedClearProgress ( TimeSpan . FromMilliseconds ( 8 0 0 ) ) ;
}
}
}
}
private void DelayedClearProgress ( TimeSpan delay )
private void DelayedClearProgress ( TimeSpan delay )
{
{
Task . Delay ( delay ) . ContinueWith ( _ = >
Task . Delay ( delay ) . ContinueWith ( _ = >