Browse Source

Merge branch 'main' into setup-dependencies

pull/5/head^2
Ionite 1 year ago committed by GitHub
parent
commit
5b71578909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      StabilityMatrix/App.xaml.cs
  2. 11
      StabilityMatrix/CheckpointBrowserPage.xaml
  3. 41
      StabilityMatrix/Converters/Json/DefaultUnknownEnumConverter.cs
  4. 3
      StabilityMatrix/Helper/IPrerequisiteHelper.cs
  5. 7
      StabilityMatrix/Helper/PrerequisiteHelper.cs
  6. 4
      StabilityMatrix/Models/Api/CivitModelType.cs
  7. 23
      StabilityMatrix/SettingsPage.xaml
  8. 82
      StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs
  9. 17
      StabilityMatrix/ViewModels/SettingsViewModel.cs

9
StabilityMatrix/App.xaml.cs

@ -18,7 +18,6 @@ using Microsoft.Extensions.Logging;
using NLog;
using NLog.Config;
using NLog.Extensions.Logging;
using NLog.Layouts;
using NLog.Targets;
using Octokit;
using Polly;
@ -361,9 +360,13 @@ namespace StabilityMatrix
};
var exceptionWindow = new ExceptionWindow
{
DataContext = vm,
Owner = MainWindow
DataContext = vm
};
if (MainWindow?.IsActive ?? false)
{
exceptionWindow.Owner = MainWindow;
}
exceptionWindow.ShowDialog();
}
e.Handled = true;

11
StabilityMatrix/CheckpointBrowserPage.xaml

@ -139,7 +139,7 @@
<ui:TextBox
HorizontalAlignment="Stretch"
Margin="8,0,0,0"
PlaceholderText="Query"
PlaceholderText="Search models, #tags, or @users"
Text="{Binding SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ui:Button
@ -238,6 +238,15 @@
</StackPanel>
</StackPanel>
<TextBlock
Grid.Row="0"
Grid.RowSpan="3"
FontSize="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding NoResultsText, FallbackValue=No results found}"
Visibility="{Binding NoResultsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<ui:ProgressRing
Grid.Row="0"
Grid.RowSpan="3"

41
StabilityMatrix/Converters/Json/DefaultUnknownEnumConverter.cs

@ -0,0 +1,41 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using StabilityMatrix.Extensions;
namespace StabilityMatrix.Converters.Json;
public class DefaultUnknownEnumConverter<T> : JsonConverter<T> where T : Enum
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
var enumText = reader.GetString();
if (Enum.TryParse(typeof(T), enumText, true, out var result))
{
return (T) result!;
}
// Unknown value handling
if (Enum.TryParse(typeof(T), "Unknown", true, out var unknownResult))
{
return (T) unknownResult!;
}
throw new JsonException($"Unable to parse '{enumText}' to enum '{typeof(T)}'.");
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStringValue(value.GetStringValue());
}
}

3
StabilityMatrix/Helper/IPrerequisiteHelper.cs

@ -8,10 +8,13 @@ public interface IPrerequisiteHelper
{
string GitBinPath { get; }
bool IsPythonInstalled { get; }
Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null);
Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null);
Task InstallGitIfNecessary(IProgress<ProgressReport>? progress = null);
Task InstallVcRedistIfNecessary(IProgress<ProgressReport>? progress = null);
Task InstallPythonIfNecessary(IProgress<ProgressReport>? progress = null);
/// <summary>
/// Run embedded git with the given arguments.

7
StabilityMatrix/Helper/PrerequisiteHelper.cs

@ -42,6 +42,8 @@ public class PrerequisiteHelper : IPrerequisiteHelper
private string PortableGitDownloadPath => Path.Combine(HomeDir, "PortableGit.7z.exe");
private string GitExePath => Path.Combine(PortableGitInstallDir, "bin", "git.exe");
public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin");
public bool IsPythonInstalled => File.Exists(PythonDllPath);
public PrerequisiteHelper(ILogger<PrerequisiteHelper> logger, IGitHubClient gitHubClient,
IDownloadService downloadService, ISettingsManager settingsManager)
@ -215,9 +217,8 @@ public class PrerequisiteHelper : IPrerequisiteHelper
logger.LogInformation("Git not found at {GitExePath}, downloading...", GitExePath);
var latestRelease = await gitHubClient.Repository.Release.GetLatest("git-for-windows", "git");
var portableGitUrl = latestRelease.Assets
.First(a => a.Name.EndsWith("64-bit.7z.exe")).BrowserDownloadUrl;
var portableGitUrl =
"https://github.com/git-for-windows/git/releases/download/v2.41.0.windows.1/PortableGit-2.41.0-64-bit.7z.exe";
if (!File.Exists(PortableGitDownloadPath))
{

4
StabilityMatrix/Models/Api/CivitModelType.cs

@ -1,13 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using StabilityMatrix.Converters.Json;
using StabilityMatrix.Extensions;
namespace StabilityMatrix.Models.Api;
[JsonConverter(typeof(JsonStringEnumConverter))]
[JsonConverter(typeof(DefaultUnknownEnumConverter<CivitModelType>))]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum CivitModelType
{
Unknown,
[ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)]
Checkpoint,
[ConvertTo<SharedFolderType>(SharedFolderType.TextualInversion)]

23
StabilityMatrix/SettingsPage.xaml

@ -133,10 +133,25 @@
FontWeight="Bold"
Margin="0,8"
Text="Embedded Python" />
<Button
Command="{Binding PythonVersionCommand}"
Content="Check Version Info"
Margin="8" />
<StackPanel Orientation="Horizontal">
<ui:Button
Command="{Binding PythonVersionCommand}"
Content="Check Version Info"
Margin="8" />
<!-- Progress for python install if needed -->
<StackPanel Orientation="Horizontal" Visibility="{Binding IsPythonInstalling, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Collapsed}">
<ui:ProgressRing
Height="24"
IsEnabled="{Binding IsPythonInstalling}"
IsIndeterminate="True"
Margin="8"
Width="24" />
<TextBlock
Margin="4"
Text="Preparing Environment"
VerticalAlignment="Center" />
</StackPanel>
</StackPanel>
</StackPanel>
</ui:Card>

82
StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs

@ -46,13 +46,15 @@ public partial class CheckpointBrowserViewModel : ObservableObject
[ObservableProperty] private bool canGoToNextPage;
[ObservableProperty] private bool canGoToPreviousPage;
[ObservableProperty] private bool isIndeterminate;
[ObservableProperty] private bool noResultsFound;
[ObservableProperty] private string noResultsText;
public IEnumerable<CivitPeriod> AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();
public IEnumerable<CivitSortMode> AllSortModes => Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>();
public IEnumerable<CivitModelType> AllModelTypes => Enum.GetValues(typeof(CivitModelType))
.Cast<CivitModelType>()
.Where(t => t != CivitModelType.AestheticGradient && t != CivitModelType.Poses)
.Where(t => t == CivitModelType.All || t.ConvertTo<SharedFolderType>() > 0)
.OrderBy(t => t.ToString());
public CheckpointBrowserViewModel(
@ -100,10 +102,25 @@ public partial class CheckpointBrowserViewModel : ObservableObject
var models = modelsResponse.Items;
if (models is null)
{
Logger.Debug("CivitAI Query {Text} returned no results (in {Elapsed:F1} s)", queryText, timer.Elapsed.TotalSeconds);
Logger.Debug("CivitAI Query {Text} returned no results (in {Elapsed:F1} s)",
queryText, timer.Elapsed.TotalSeconds);
return;
}
Logger.Debug("CivitAI Query {Text} returned {Results} results (in {Elapsed:F1} s)", queryText, models.Count, timer.Elapsed.TotalSeconds);
Logger.Debug("CivitAI Query {Text} returned {Results} results (in {Elapsed:F1} s)",
queryText, models.Count, timer.Elapsed.TotalSeconds);
var unknown = models.Where(m => m.Type == CivitModelType.Unknown).ToList();
if (unknown.Any())
{
var names = unknown.Select(m => m.Name).ToList();
Logger.Warn("Excluded {Unknown} unknown model types: {Models}", unknown.Count,
names);
}
// Filter out unknown model types
models = models.Where(m => m.Type.ConvertTo<SharedFolderType>() > 0).ToList();
// Database update calls will invoke `OnModelsUpdated`
// Add to database
await liteDbContext.UpsertCivitModelAsync(models);
@ -116,7 +133,7 @@ public partial class CheckpointBrowserViewModel : ObservableObject
Items = models,
Metadata = modelsResponse.Metadata
});
if (cacheNew)
{
Logger.Debug("New cache entry, updating model cards");
@ -133,6 +150,11 @@ public partial class CheckpointBrowserViewModel : ObservableObject
"CivitAI can't be reached right now").SafeFireAndForget();
Logger.Log(LogLevel.Error, e);
}
finally
{
ShowMainLoadingSpinner = false;
UpdateResultsText();
}
}
/// <summary>
@ -160,23 +182,43 @@ public partial class CheckpointBrowserViewModel : ObservableObject
HasSearched = true;
}
private string previousSearchQuery = string.Empty;
[RelayCommand]
private async Task SearchModels()
{
if (string.IsNullOrWhiteSpace(SearchQuery)) return;
var timer = Stopwatch.StartNew();
if (SearchQuery != previousSearchQuery)
{
// Reset page number
CurrentPageNumber = 1;
previousSearchQuery = SearchQuery;
}
// Build request
var modelRequest = new CivitModelsRequest
{
Query = SearchQuery,
Limit = MaxModelsPerPage,
Nsfw = "true", // Handled by local view filter
Sort = SortMode,
Period = SelectedPeriod,
Page = CurrentPageNumber,
};
if (SearchQuery.StartsWith("#"))
{
modelRequest.Tag = SearchQuery[1..];
}
else if (SearchQuery.StartsWith("@"))
{
modelRequest.Username = SearchQuery[1..];
}
else
{
modelRequest.Query = SearchQuery;
}
if (SelectedModelType != CivitModelType.All)
{
@ -189,7 +231,7 @@ public partial class CheckpointBrowserViewModel : ObservableObject
.FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest));
// If cached, update model cards
if (cachedQuery?.Items is not null && cachedQuery.Items.Any())
if (cachedQuery is not null)
{
var elapsed = timer.Elapsed;
Logger.Debug("Using cached query for {Text} [{RequestHash}] (in {Elapsed:F1} s)",
@ -199,14 +241,13 @@ public partial class CheckpointBrowserViewModel : ObservableObject
// Start remote query (background mode)
// Skip when last query was less than 2 min ago
var timeSinceCache = DateTimeOffset.UtcNow - cachedQuery.InsertedAt;
if (timeSinceCache?.TotalMinutes < 2)
if (timeSinceCache?.TotalMinutes >= 2)
{
Logger.Debug("Cached query was less than 2 minutes ago ({Seconds:F0} s), skipping remote query",
CivitModelQuery(modelRequest).SafeFireAndForget();
Logger.Debug(
"Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query",
timeSinceCache.Value.TotalSeconds);
return;
}
CivitModelQuery(modelRequest).SafeFireAndForget();
}
else
{
@ -214,6 +255,8 @@ public partial class CheckpointBrowserViewModel : ObservableObject
ShowMainLoadingSpinner = true;
await CivitModelQuery(modelRequest);
}
UpdateResultsText();
}
[RelayCommand]
@ -251,6 +294,11 @@ public partial class CheckpointBrowserViewModel : ObservableObject
{
settingsManager.SetModelBrowserNsfwEnabled(value);
ModelCardsView?.Refresh();
if (!HasSearched)
return;
UpdateResultsText();
}
partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue)
@ -281,4 +329,12 @@ public partial class CheckpointBrowserViewModel : ObservableObject
// execute command instead of calling method directly so that the IsRunning property gets updated
await SearchModelsCommand.ExecuteAsync(null);
}
private void UpdateResultsText()
{
NoResultsFound = ModelCardsView?.IsEmpty ?? true;
NoResultsText = ModelCards?.Count > 0
? $"{ModelCards.Count} results hidden by filters"
: "No results found";
}
}

17
StabilityMatrix/ViewModels/SettingsViewModel.cs

@ -41,6 +41,7 @@ public partial class SettingsViewModel : ObservableObject
private readonly IPyRunner pyRunner;
private readonly ISnackbarService snackbarService;
private readonly ILiteDbContext liteDbContext;
private readonly IPrerequisiteHelper prerequisiteHelper;
private static string LicensesPath => "pack://application:,,,/Assets/licenses.json";
public TextToFlowDocumentConverter? TextToFlowDocumentConverter { get; set; }
@ -62,6 +63,8 @@ public partial class SettingsViewModel : ObservableObject
[ObservableProperty] private bool isFileSearchFlyoutOpen;
[ObservableProperty] private double fileSearchProgress;
[ObservableProperty] private bool isPythonInstalling;
[ObservableProperty] private string? webApiHost;
[ObservableProperty] private string? webApiPort;
[ObservableProperty] private string? webApiActivePackageHost;
@ -94,7 +97,8 @@ public partial class SettingsViewModel : ObservableObject
ISnackbarService snackbarService,
ILogger<SettingsViewModel> logger,
IPackageFactory packageFactory,
ILiteDbContext liteDbContext)
ILiteDbContext liteDbContext,
IPrerequisiteHelper prerequisiteHelper)
{
this.logger = logger;
this.settingsManager = settingsManager;
@ -104,6 +108,7 @@ public partial class SettingsViewModel : ObservableObject
this.a3WebApiManager = a3WebApiManager;
this.pyRunner = pyRunner;
this.liteDbContext = liteDbContext;
this.prerequisiteHelper = prerequisiteHelper;
SelectedTheme = settingsManager.Settings.Theme ?? "Dark";
WindowBackdropType = settingsManager.Settings.WindowBackdropType ?? WindowBackdropType.Mica;
}
@ -169,6 +174,16 @@ public partial class SettingsViewModel : ObservableObject
public AsyncRelayCommand PythonVersionCommand => new(async () =>
{
// Ensure python installed
if (!prerequisiteHelper.IsPythonInstalled)
{
IsPythonInstalling = true;
// Need 7z as well for site packages repack
await prerequisiteHelper.UnpackResourcesIfNecessary();
await prerequisiteHelper.InstallPythonIfNecessary();
IsPythonInstalling = false;
}
// Get python version
await pyRunner.Initialize();
var result = await pyRunner.GetVersionInfo();

Loading…
Cancel
Save