Browse Source

add Find in Model Browser button on Checkpoints page context menu & some backend stuff for checking model updates

pull/438/head
JT 10 months ago
parent
commit
3881c2e52d
  1. 1
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 5
      StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs
  3. 39
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs
  4. 37
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
  5. 12
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
  6. 13
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
  7. 15
      StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs
  8. 2
      StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs
  9. 3
      StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml
  10. 346
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
  11. 7
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  12. 4
      StabilityMatrix.Core/Helper/EventManager.cs
  13. 45
      StabilityMatrix.Core/Helper/ModelFinder.cs
  14. 10
      StabilityMatrix.Core/Models/Database/LocalModelFile.cs
  15. 45
      StabilityMatrix.Core/Services/ModelIndexService.cs

1
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -209,6 +209,7 @@ public static class DesignData
PreviewImagePath =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/"
+ "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg",
UpdateAvailable = true,
ConnectedModel = new ConnectedModelInfo
{
VersionName = "Lightning Auroral",

5
StabilityMatrix.Avalonia/DesignData/MockModelIndexService.cs

@ -43,6 +43,11 @@ public class MockModelIndexService : IModelIndexService
return Task.FromResult(false);
}
public Task CheckModelsForUpdates()
{
return Task.CompletedTask;
}
/// <inheritdoc />
public void BackgroundRefreshIndex() { }
}

39
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CivitAiBrowserViewModel.cs

@ -18,6 +18,7 @@ using CommunityToolkit.Mvvm.Input;
using LiteDB;
using LiteDB.Async;
using NLog;
using OneOf.Types;
using Refit;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
@ -160,6 +161,17 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
.Where(page => page <= TotalPages && page > 0)
.ObserveOn(SynchronizationContext.Current)
.Subscribe(_ => TrySearchAgain(false).SafeFireAndForget(), err => Logger.Error(err));
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
}
private void OnNavigateAndFindCivitModelRequested(object? sender, int e)
{
if (e <= 0)
return;
SearchQuery = $"$#{e}";
SearchModelsCommand.ExecuteAsync(null).SafeFireAndForget();
}
public override void OnLoaded()
@ -399,6 +411,16 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
Page = CurrentPageNumber
};
if (SelectedModelType != CivitModelType.All)
{
modelRequest.Types = [SelectedModelType];
}
if (SelectedBaseModelType != "All")
{
modelRequest.BaseModel = SelectedBaseModelType;
}
if (SearchQuery.StartsWith("#"))
{
modelRequest.Tag = SearchQuery[1..];
@ -407,19 +429,16 @@ public partial class CivitAiBrowserViewModel : TabViewModelBase
{
modelRequest.Username = SearchQuery[1..];
}
else
{
modelRequest.Query = SearchQuery;
}
if (SelectedModelType != CivitModelType.All)
else if (SearchQuery.StartsWith("$#"))
{
modelRequest.Types = [SelectedModelType];
modelRequest.Period = CivitPeriod.AllTime;
modelRequest.BaseModel = null;
modelRequest.Types = null;
modelRequest.CommaSeparatedModelIds = SearchQuery[2..];
}
if (SelectedBaseModelType != "All")
else
{
modelRequest.BaseModel = SelectedBaseModelType;
modelRequest.Query = SearchQuery;
}
if (SortMode == CivitSortMode.Installed)

37
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

@ -1,13 +1,16 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
@ -15,18 +18,34 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(CheckpointBrowserPage))]
[Singleton]
public partial class CheckpointBrowserViewModel(
CivitAiBrowserViewModel civitAiBrowserViewModel,
HuggingFacePageViewModel huggingFaceViewModel
) : PageViewModelBase
public partial class CheckpointBrowserViewModel : PageViewModelBase
{
public override string Title => "Model Browser";
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.BrainCircuit, IsFilled = true };
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.BrainCircuit, IsFilled = true };
public IReadOnlyList<TabItem> Pages { get; } =
new List<TabItem>(
public IReadOnlyList<TabItem> Pages { get; }
[ObservableProperty]
private TabItem? selectedPage;
/// <inheritdoc/>
public CheckpointBrowserViewModel(
CivitAiBrowserViewModel civitAiBrowserViewModel,
HuggingFacePageViewModel huggingFaceViewModel
)
{
Pages = new List<TabItem>(
new List<TabViewModelBase>([civitAiBrowserViewModel, huggingFaceViewModel]).Select(
vm => new TabItem { Header = vm.Header, Content = vm }
)
);
SelectedPage = Pages.FirstOrDefault();
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
}
private void OnNavigateAndFindCivitModelRequested(object? sender, int e)
{
SelectedPage = Pages.FirstOrDefault();
}
}

12
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs

@ -63,6 +63,9 @@ public partial class CheckpointFile : ViewModelBase
[ObservableProperty]
private ProgressReport? progress;
[ObservableProperty]
private bool updateAvailable;
public string FileName => Path.GetFileName(FilePath);
public bool CanShowTriggerWords =>
@ -212,6 +215,15 @@ public partial class CheckpointFile : ViewModelBase
}
}
[RelayCommand]
private void FindOnModelBrowser()
{
if (ConnectedModel?.ModelId == null)
return;
EventManager.Instance.OnNavigateAndFindCivitModelRequested(ConnectedModel.ModelId);
}
[RelayCommand]
private void OpenOnCivitAi()
{

13
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
@ -616,13 +615,13 @@ public partial class CheckpointFolder : ViewModelBase
/// </summary>
public void BackgroundIndex()
{
Dispatcher.UIThread.Post(Index, DispatcherPriority.Background);
Dispatcher.UIThread.InvokeAsync(IndexAsync, DispatcherPriority.Background);
}
/// <summary>
/// Indexes the folder for checkpoint files and refreshes the CheckPointFiles collection.
/// </summary>
public void Index()
public async Task IndexAsync()
{
var updatedFolders = new List<CheckpointFolder>();
// Get subfolders
@ -660,12 +659,10 @@ public partial class CheckpointFolder : ViewModelBase
SubFoldersCache.EditDiff(updatedFolders, (a, b) => a.Title == b.Title);
// Index files
var files = GetCheckpointFiles().ToList();
Dispatcher.UIThread.Post(
() =>
{
var files = GetCheckpointFiles();
checkpointFilesCache.EditDiff(files, CheckpointFile.FilePathComparer);
},
() => checkpointFilesCache.EditDiff(files, CheckpointFile.FilePathComparer),
DispatcherPriority.Background
);
}

15
StabilityMatrix.Avalonia/ViewModels/CheckpointsPageViewModel.cs

@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
@ -150,10 +149,9 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
.Subscribe();
}
public override void OnLoaded()
public override async Task OnLoadedAsync()
{
base.OnLoaded();
await base.OnLoadedAsync();
var sw = Stopwatch.StartNew();
// Set UI states
@ -173,8 +171,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
IsLoading = CheckpointFolders.Count == 0;
IsIndexing = CheckpointFolders.Count > 0;
// GetStuff();
IndexFolders();
await IndexFoldersAsync();
IsLoading = false;
IsIndexing = false;
@ -271,7 +268,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
) || folder.SubFolders.Any(ContainsBaseModel);
}
private void IndexFolders()
private async Task IndexFoldersAsync()
{
var modelsDirectory = settingsManager.ModelsDirectory;
@ -290,7 +287,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
// Get from cache or create new
if (CheckpointFoldersCache.Lookup(folder) is { HasValue: true } result)
{
result.Value.Index();
await result.Value.IndexAsync();
updatedFolders.Add(result.Value);
}
else
@ -307,7 +304,7 @@ public partial class CheckpointsPageViewModel : PageViewModelBase
DirectoryPath = folder,
IsExpanded = true // Top level folders expanded by default
};
checkpointFolder.Index();
await checkpointFolder.IndexAsync();
updatedFolders.Add(checkpointFolder);
}
}

2
StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
@ -11,7 +10,6 @@ using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using KGySoft.CoreLibraries;
using NLog;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;

3
StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

@ -14,5 +14,6 @@
x:DataType="viewModels:CheckpointBrowserViewModel"
mc:Ignorable="d"
x:Class="StabilityMatrix.Avalonia.Views.CheckpointBrowserPage">
<TabControl ItemsSource="{Binding Pages}"/>
<TabControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}"/>
</controls:UserControlBase>

346
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -70,7 +70,11 @@
IsVisible="{Binding CanShowTriggerWords}"
Text="{x:Static lang:Resources.Action_CopyTriggerWords}"
IconSource="Copy" />
<ui:MenuFlyoutSeparator IsVisible="{Binding CanShowTriggerWords}"/>
<ui:MenuFlyoutItem Command="{Binding FindOnModelBrowserCommand}"
IsVisible="{Binding IsConnectedModel}"
Text="Find in Model Browser"
IconSource="Find" />
<ui:MenuFlyoutSeparator IsVisible="{Binding CanShowTriggerWords}" />
<ui:MenuFlyoutItem Command="{Binding OpenOnCivitAiCommand}"
Text="{x:Static lang:Resources.Action_OpenOnCivitAi}" IconSource="Link"
IsVisible="{Binding IsConnectedModel}" />
@ -78,7 +82,7 @@
Command="{Binding CopyModelUrlCommand}"
Text="Copy Link to Clipboard"
IconSource="Clipboard"
IsVisible="{Binding IsConnectedModel}"/>
IsVisible="{Binding IsConnectedModel}" />
<ui:MenuFlyoutItem Text="{x:Static lang:Resources.Label_FindConnectedMetadata}"
IconSource="Find"
Command="{Binding FindConnectedMetadataCommand}"
@ -95,7 +99,7 @@
<system:Boolean>True</system:Boolean>
</ui:MenuFlyoutItem.CommandParameter>
</ui:MenuFlyoutItem>
<ui:MenuFlyoutSeparator/>
<ui:MenuFlyoutSeparator />
<ui:MenuFlyoutItem Command="{Binding RenameCommand}"
Text="{x:Static lang:Resources.Action_Rename}" IconSource="Rename" />
<ui:MenuFlyoutItem Command="{Binding DeleteCommand}"
@ -103,180 +107,205 @@
IconSource="Delete" />
</ui:FAMenuFlyout>
</controls:Card.ContextFlyout>
<Grid RowDefinitions="Auto, Auto">
<!-- Main contents, hidden when IsLoading is true -->
<StackPanel Grid.Row="0" Grid.RowSpan="2"
MinHeight="70" Margin="14, 16, 14, 16">
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto,*,Auto,Auto"
IsVisible="{Binding !IsLoading}">
<StackPanel
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
IsEnabled="True"
Margin="0,0,0,4"
Orientation="Vertical">
<!-- Tooltip for title and version -->
<ToolTip.Tip>
<StackPanel>
<!-- Title -->
<TextBlock
FontSize="14"
FontWeight="Medium"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
HorizontalAlignment="Left"
Margin="0,0,0,4"
Text="{Binding Title}"
TextWrapping="WrapWithOverflow" />
<!-- Version -->
<TextBlock
FontSize="13"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
Margin="0,0,0,4"
Text="{Binding ConnectedModel.VersionName, FallbackValue=''}"
TextWrapping="WrapWithOverflow"
IsVisible="{Binding IsConnectedModel}" />
<!-- Image -->
<controls:BetterAdvancedImage
Margin="0,0,0,4"
CornerRadius="4"
Source="{Binding PreviewImagePath}"
Stretch="Uniform"
IsVisible="{Binding IsConnectedModel}" />
<TextBlock
FontSize="13"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
Margin="0,0,0,4"
TextWrapping="WrapWithOverflow"
IsVisible="{Binding CanShowTriggerWords}">
<Run Text="{x:Static lang:Resources.Label_TriggerWords}" />
<Run
Text="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}" />
</TextBlock>
</StackPanel>
</ToolTip.Tip>
<!-- Main contents -->
<Grid ColumnDefinitions="*, Auto"
RowDefinitions="Auto,*,Auto,Auto"
MinHeight="70" Margin="14, 16, 14, 16">
<StackPanel
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Margin="0,0,0,4"
Orientation="Vertical">
<!-- Tooltip for title and version -->
<ToolTip.Tip>
<StackPanel>
<!-- Title -->
<TextBlock
FontSize="14"
FontWeight="Medium"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
HorizontalAlignment="Left"
IsEnabled="True"
MaxWidth="250"
Margin="0,0,0,4"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
TextWrapping="WrapWithOverflow" />
<!-- Version -->
<TextBlock
FontSize="13"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
IsEnabled="True"
Margin="0,0,0,4"
Text="{Binding ConnectedModel.VersionName, FallbackValue=''}"
TextWrapping="WrapWithOverflow"
IsVisible="{Binding IsConnectedModel}" />
<!-- Image -->
<controls:BetterAdvancedImage
Margin="0,0,0,4"
CornerRadius="4"
Source="{Binding PreviewImagePath}"
Stretch="Uniform"
IsVisible="{Binding IsConnectedModel}" />
<TextBlock
FontSize="13"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
Margin="0,0,0,4"
TextWrapping="WrapWithOverflow"
IsVisible="{Binding CanShowTriggerWords}">
<Run Text="{x:Static lang:Resources.Label_TriggerWords}" />
<Run
Text="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}" />
</TextBlock>
</StackPanel>
</ToolTip.Tip>
<TextBlock
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
HorizontalAlignment="Left"
IsEnabled="True"
MaxWidth="250"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock
FontSize="13"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
IsEnabled="True"
Text="{Binding ConnectedModel.VersionName, FallbackValue=''}"
TextWrapping="WrapWithOverflow"
IsVisible="{Binding IsConnectedModel}" />
</StackPanel>
<controls:BetterAdvancedImage Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0, 4,0,4"
CornerRadius="8"
Source="{Binding PreviewImagePath}"
Stretch="UniformToFill"
Height="300"
Width="300"
IsVisible="{Binding $parent[ItemsControl].((vm:CheckpointsPageViewModel)DataContext).ShowConnectedModelImages}" />
<StackPanel Orientation="Horizontal"
Grid.Column="1"
Grid.Row="0"
HorizontalAlignment="Right">
<controls:Card
Height="24"
Margin="0,0,8,0"
Padding="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes="success"
IsVisible="{Binding UpdateAvailable}">
<controls:BetterAdvancedImage Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0, 4,0,4"
CornerRadius="8"
Source="{Binding PreviewImagePath}"
Stretch="UniformToFill"
Height="300"
Width="300"
IsVisible="{Binding $parent[ItemsControl].((vm:CheckpointsPageViewModel)DataContext).ShowConnectedModelImages}" />
<Grid Grid.Column="1" Grid.Row="0">
<Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="True"
MaxHeight="48"
MaxWidth="64"
Padding="0"
ToolTip.Tip="{x:Static lang:Resources.Label_ConnectedModel}"
VerticalAlignment="Top"
IsVisible="{Binding IsConnectedModel, Mode=OneWay}">
<ui:SymbolIcon
Foreground="LightGreen"
Symbol="CloudSyncComplete"
ToolTip.Tip="{x:Static lang:Resources.Label_ConnectedModel}" />
</Button>
<Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="True"
MaxHeight="48"
MaxWidth="64"
Padding="0"
ToolTip.Tip="{x:Static lang:Resources.Label_LocalModel}"
VerticalAlignment="Top"
IsVisible="{Binding !IsConnectedModel, Mode=OneWay}">
<ui:SymbolIcon
Foreground="DimGray"
Symbol="CloudOffline"
ToolTip.Tip="{x:Static lang:Resources.Label_LocalModel}" />
</Button>
</Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Grid.Column="0"
Grid.Row="2"
IsEnabled="True"
Margin="0,4"
Text="{Binding FileName}"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding FileName}"
VerticalAlignment="Bottom" />
<ItemsRepeater
Grid.Column="1"
Grid.Row="2"
HorizontalAlignment="Right"
ItemTemplate="{StaticResource BadgeTemplate}"
ItemsSource="{Binding Badges}"
VerticalAlignment="Bottom">
<ItemsRepeater.Layout>
<UniformGridLayout />
</ItemsRepeater.Layout>
</ItemsRepeater>
<TextBlock Grid.Row="3" Grid.Column="0"
Grid.ColumnSpan="2"
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="0,4,0,0"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTip.Tip="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}"
IsVisible="{Binding CanShowTriggerWords}">
<Run Text="{x:Static lang:Resources.Label_TriggerWords}" />
<Run Text="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}" />
</TextBlock>
</Grid>
FontWeight="Medium"
Text="Update Available" />
</controls:Card>
<Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="True"
MaxHeight="48"
MaxWidth="64"
Padding="0"
ToolTip.Tip="{x:Static lang:Resources.Label_ConnectedModel}"
VerticalAlignment="Top"
IsVisible="{Binding IsConnectedModel, Mode=OneWay}">
<ui:SymbolIcon
Foreground="LightGreen"
Symbol="CloudSyncComplete"
ToolTip.Tip="{x:Static lang:Resources.Label_ConnectedModel}" />
</Button>
<Button
Background="Transparent"
BorderBrush="Transparent"
FontSize="20"
HorizontalAlignment="Right"
IsEnabled="True"
MaxHeight="48"
MaxWidth="64"
Padding="0"
ToolTip.Tip="{x:Static lang:Resources.Label_LocalModel}"
VerticalAlignment="Top"
IsVisible="{Binding !IsConnectedModel, Mode=OneWay}">
<ui:SymbolIcon
Foreground="DimGray"
Symbol="CloudOffline"
ToolTip.Tip="{x:Static lang:Resources.Label_LocalModel}" />
</Button>
</StackPanel>
<Border Grid.Row="0"
Grid.RowSpan="2"
Background="#AA000000"
<TextBlock
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Grid.Column="0"
Grid.Row="2"
IsEnabled="True"
Margin="0,4"
Text="{Binding FileName}"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding FileName}"
VerticalAlignment="Bottom" />
<ItemsRepeater
Grid.Column="1"
Grid.Row="2"
HorizontalAlignment="Right"
ItemTemplate="{StaticResource BadgeTemplate}"
ItemsSource="{Binding Badges}"
VerticalAlignment="Bottom">
<ItemsRepeater.Layout>
<UniformGridLayout />
</ItemsRepeater.Layout>
</ItemsRepeater>
<TextBlock Grid.Row="3" Grid.Column="0"
Grid.ColumnSpan="2"
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="0,4,0,0"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTip.Tip="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}"
IsVisible="{Binding CanShowTriggerWords}">
<Run Text="{x:Static lang:Resources.Label_TriggerWords}" />
<Run Text="{Binding ConnectedModel.TrainedWordsString, FallbackValue=''}" />
</TextBlock>
<Border Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.RowSpan="4"
Margin="-14, -16"
Background="#BB000000"
IsVisible="{Binding IsLoading}"
CornerRadius="12"/>
CornerRadius="12" />
<TextBlock Grid.Row="1"
<TextBlock Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.RowSpan="4"
x:CompileBindings="False"
Text="{Binding Progress.Title}"
Margin="0,64,0,0"
Text="{Binding Progress.Title, FallbackValue=Test}"
TextAlignment="Center"
IsVisible="{Binding IsLoading}"
VerticalAlignment="Center"/>
VerticalAlignment="Center" />
<!-- Progress ring -->
<controls:ProgressRing
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.RowSpan="4"
Height="32"
Width="32"
Foreground="{DynamicResource ThemeRedColor}"
@ -509,7 +538,7 @@
HorizontalAlignment="Stretch"
Classes="transparent"
Command="{Binding ClearOrSelectAllBaseModels}"
Margin="0,0,0,8"/>
Margin="0,0,0,8" />
<ListBox ItemsSource="{Binding BaseModelOptions}"
SelectionMode="Multiple, Toggle"
SelectedItems="{Binding SelectedBaseModels}">
@ -520,7 +549,7 @@
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type checkpointManager:BaseModelOptionViewModel}">
<TextBlock Text="{Binding ModelType}"/>
<TextBlock Text="{Binding ModelType}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
@ -530,7 +559,6 @@
</DropDownButton>
</StackPanel>
<ui:CommandBar
Grid.Row="0"
@ -556,11 +584,11 @@
<Grid ColumnDefinitions="Auto, Auto">
<ui:SymbolIcon Grid.Column="0"
Symbol="CloudDownload"
FontSize="18"/>
FontSize="18" />
<TextBlock Grid.Column="1"
Text="{x:Static lang:Resources.Label_FindConnectedMetadata}"
Margin="8,0,0,0"
VerticalAlignment="Center"/>
VerticalAlignment="Center" />
</Grid>
</SplitButton.Content>
@ -568,7 +596,7 @@
<ui:FAMenuFlyout
Placement="Bottom">
<ui:MenuFlyoutItem Text="{x:Static lang:Resources.Action_UpdateExistingMetadata}"
Command="{Binding UpdateExistingMetadataCommand}"/>
Command="{Binding UpdateExistingMetadataCommand}" />
</ui:FAMenuFlyout>
</SplitButton.Flyout>
</SplitButton>
@ -613,9 +641,9 @@
Orientation="Vertical">
<TextBlock Text="{Binding Progress.Title}"
TextAlignment="Center"
Margin="0,0,0,4"/>
Margin="0,0,0,4" />
<ProgressBar Value="{Binding Progress.Percentage}"
Margin="16,4,16,16"/>
Margin="16,4,16,16" />
</StackPanel>
<StackPanel

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

@ -34,6 +34,7 @@ using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
@ -95,6 +96,7 @@ public partial class MainWindow : AppWindowBase
EventManager.Instance.ToggleProgressFlyout += (_, _) => progressFlyout?.Hide();
EventManager.Instance.CultureChanged += (_, _) => SetDefaultFonts();
EventManager.Instance.UpdateAvailable += OnUpdateAvailable;
EventManager.Instance.NavigateAndFindCivitModelRequested += OnNavigateAndFindCivitModelRequested;
Observable
.FromEventPattern<SizeChangedEventArgs>(this, nameof(SizeChanged))
@ -138,6 +140,11 @@ public partial class MainWindow : AppWindowBase
});
}
private void OnNavigateAndFindCivitModelRequested(object? sender, int e)
{
navigationService.NavigateTo<CheckpointBrowserViewModel>();
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{

4
StabilityMatrix.Core/Helper/EventManager.cs

@ -39,6 +39,7 @@ public class EventManager
public event EventHandler<LocalImageFile>? InferenceUpscaleRequested;
public event EventHandler<LocalImageFile>? InferenceImageToImageRequested;
public event EventHandler<LocalImageFile>? InferenceImageToVideoRequested;
public event EventHandler<int>? NavigateAndFindCivitModelRequested;
public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);
@ -86,4 +87,7 @@ public class EventManager
public void OnInferenceImageToVideoRequested(LocalImageFile imageFile) =>
InferenceImageToVideoRequested?.Invoke(this, imageFile);
public void OnNavigateAndFindCivitModelRequested(int modelId) =>
NavigateAndFindCivitModelRequested?.Invoke(this, modelId);
}

45
StabilityMatrix.Core/Helper/ModelFinder.cs

@ -56,9 +56,9 @@ public class ModelFinder
// VersionResponse is not actually the full data of ModelVersion, so find it again
var version = model.ModelVersions!.First(version => version.Id == versionResponse.Id);
var file = versionResponse
.Files
.First(file => hashBlake3.Equals(file.Hashes.BLAKE3, StringComparison.OrdinalIgnoreCase));
var file = versionResponse.Files.First(
file => hashBlake3.Equals(file.Hashes.BLAKE3, StringComparison.OrdinalIgnoreCase)
);
return new ModelSearchResult(model, version, file);
}
@ -79,7 +79,12 @@ public class ModelFinder
}
else
{
Logger.Warn(e, "Could not find remote model version using hash {Hash}: {Error}", hashBlake3, e.Message);
Logger.Warn(
e,
"Could not find remote model version using hash {Hash}: {Error}",
hashBlake3,
e.Message
);
}
return null;
@ -95,4 +100,36 @@ public class ModelFinder
return null;
}
}
public async Task<IEnumerable<CivitModel>> FindRemoteModelsById(IEnumerable<int> ids)
{
try
{
// split ids into batches of 20
var batches = ids.Select((id, index) => (id, index))
.GroupBy(tuple => tuple.index / 20)
.Select(group => group.Select(tuple => tuple.id));
var results = new List<CivitModel>();
foreach (var batch in batches)
{
var response = await civitApi
.GetModels(new CivitModelsRequest { CommaSeparatedModelIds = string.Join(",", batch) })
.ConfigureAwait(false);
if (response.Items == null || response.Items.Count == 0)
continue;
results.AddRange(response.Items);
}
return results;
}
catch (Exception e)
{
Logger.Error("Error while finding remote models by id: {Error}", e.Message);
}
return Enumerable.Empty<CivitModel>();
}
}

10
StabilityMatrix.Core/Models/Database/LocalModelFile.cs

@ -34,6 +34,16 @@ public record LocalModelFile
/// </summary>
public string? PreviewImageFullPath { get; set; }
/// <summary>
/// Whether or not an update is available for this model
/// </summary>
public bool HasUpdate { get; set; }
/// <summary>
/// Last time this model was checked for an update
/// </summary>
public DateTimeOffset LastUpdateCheck { get; set; }
/// <summary>
/// File name of the relative path.
/// </summary>

45
StabilityMatrix.Core/Services/ModelIndexService.cs

@ -1,6 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics;
using System.Text;
using AsyncAwaitBestPractices;
using AutoCtor;
@ -10,6 +8,7 @@ using StabilityMatrix.Core.Database;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
@ -22,6 +21,7 @@ public partial class ModelIndexService : IModelIndexService
private readonly ILogger<ModelIndexService> logger;
private readonly ISettingsManager settingsManager;
private readonly ILiteDbContext liteDbContext;
private readonly ModelFinder modelFinder;
public Dictionary<SharedFolderType, List<LocalModelFile>> ModelIndex { get; private set; } = new();
@ -219,4 +219,43 @@ public partial class ModelIndexService : IModelIndexService
return false;
}
// idk do somethin with this
public async Task CheckModelsForUpdateAsync()
{
var installedHashes = settingsManager.Settings.InstalledModelHashes;
var dbModels = (
await liteDbContext.LocalModelFiles.FindAllAsync().ConfigureAwait(false)
?? Enumerable.Empty<LocalModelFile>()
).ToList();
var ids = dbModels
.Where(x => x.ConnectedModelInfo != null)
.Where(
x => x.LastUpdateCheck == default || x.LastUpdateCheck < DateTimeOffset.UtcNow.AddHours(-8)
)
.Select(x => x.ConnectedModelInfo!.ModelId);
var remoteModels = (await modelFinder.FindRemoteModelsById(ids).ConfigureAwait(false)).ToList();
foreach (var dbModel in dbModels)
{
if (dbModel.ConnectedModelInfo == null)
continue;
var remoteModel = remoteModels.FirstOrDefault(m => m.Id == dbModel.ConnectedModelInfo!.ModelId);
var latestVersion = remoteModel?.ModelVersions?.FirstOrDefault();
var latestHashes = latestVersion
?.Files
?.Where(f => f.Type == CivitFileType.Model)
.Select(f => f.Hashes.BLAKE3);
if (latestHashes == null)
continue;
dbModel.HasUpdate = !latestHashes.Any(hash => installedHashes?.Contains(hash) ?? false);
dbModel.LastUpdateCheck = DateTimeOffset.UtcNow;
await liteDbContext.LocalModelFiles.UpsertAsync(dbModel).ConfigureAwait(false);
}
}
}

Loading…
Cancel
Save