Browse Source
# Conflicts: # StabilityMatrix.Avalonia/App.axaml.cs # StabilityMatrix.Avalonia/Controls/StackExpander.axaml # StabilityMatrix.Avalonia/DesignData/DesignData.cs # StabilityMatrix.Avalonia/Languages/Resources.resx # StabilityMatrix.Core/Helper/FileHash.cs # StabilityMatrix.Core/Models/Packages/A3WebUI.cs # StabilityMatrix.Core/Models/Packages/ComfyUI.cs # StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs # StabilityMatrix.Core/Models/Packages/VladAutomatic.cs # StabilityMatrix.Core/Services/SettingsManager.cspull/333/head
Ionite
12 months ago
59 changed files with 3398 additions and 1799 deletions
@ -0,0 +1,544 @@
|
||||
[ |
||||
{ |
||||
"ModelCategory": "BaseModel", |
||||
"ModelName": "Stable Diffusion 1.5", |
||||
"RepositoryPath": "runwayml/stable-diffusion-v1-5", |
||||
"Files": [ |
||||
"v1-5-pruned-emaonly.safetensors" |
||||
], |
||||
"LicenseType": "CreativeML Open RAIL-M" |
||||
}, |
||||
{ |
||||
"ModelCategory": "BaseModel", |
||||
"ModelName": "Stable Diffusion 2.1", |
||||
"RepositoryPath": "stabilityai/stable-diffusion-2-1", |
||||
"Files": [ |
||||
"v2-1_768-ema-pruned.safetensors" |
||||
], |
||||
"LicenseType": "Open RAIL++" |
||||
}, |
||||
{ |
||||
"ModelCategory": "BaseModel", |
||||
"ModelName": "Stable Diffusion XL (Base)", |
||||
"RepositoryPath": "stabilityai/stable-diffusion-xl-base-1.0", |
||||
"Files": [ |
||||
"sd_xl_base_1.0_0.9vae.safetensors", |
||||
"sd_xl_offset_example-lora_1.0.safetensors" |
||||
], |
||||
"LicenseType": "Open RAIL++", |
||||
"LicensePath": "LICENSE.md" |
||||
}, |
||||
{ |
||||
"ModelCategory": "BaseModel", |
||||
"ModelName": "Stable Diffusion XL (Refiner)", |
||||
"RepositoryPath": "stabilityai/stable-diffusion-xl-refiner-1.0", |
||||
"Files": [ |
||||
"sd_xl_refiner_1.0_0.9vae.safetensors" |
||||
], |
||||
"LicenseType": "Open RAIL++", |
||||
"LicensePath": "LICENSE.md" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Canny", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_canny.pth", |
||||
"control_v11p_sd15_canny.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Depth", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11f1p_sd15_depth.pth", |
||||
"control_v11f1p_sd15_depth.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "MLSD", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_mlsd.pth", |
||||
"control_v11p_sd15_mlsd.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Inpaint", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_inpaint.pth", |
||||
"control_v11p_sd15_inpaint.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "IP2P", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11e_sd15_ip2p.pth", |
||||
"control_v11e_sd15_ip2p.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Tile", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11f1e_sd15_tile.pth", |
||||
"control_v11f1e_sd15_tile.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "OpenPose", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_openpose.pth", |
||||
"control_v11p_sd15_openpose.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "LineArt", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_lineart.pth", |
||||
"control_v11p_sd15_lineart.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "LineArt Anime", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15s2_lineart_anime.pth", |
||||
"control_v11p_sd15s2_lineart_anime.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "NormalBae", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_normalbae.pth", |
||||
"control_v11p_sd15_normalbae.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Seg", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_seg.pth", |
||||
"control_v11p_sd15_seg.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Soft Edge", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_softedge.pth", |
||||
"control_v11p_sd15_softedge.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Scribble", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11p_sd15_scribble.pth", |
||||
"control_v11p_sd15_scribble.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "ControlNet", |
||||
"ModelName": "Shuffle", |
||||
"RepositoryPath": "lllyasviel/ControlNet-v1-1", |
||||
"Files": [ |
||||
"control_v11e_sd15_shuffle.pth", |
||||
"control_v11e_sd15_shuffle.yaml" |
||||
], |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Canny", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_canny", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "canny", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Depth", |
||||
"RepositoryPath": "lllyasviel/control_v11f1p_sd15_depth", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "depth", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "MLSD", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_mlsd", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "MLSD", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Inpaint", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_inpaint", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "inpaint", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "IP2P", |
||||
"RepositoryPath": "lllyasviel/control_v11e_sd15_ip2p", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "ip2p", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Tile", |
||||
"RepositoryPath": "lllyasviel/control_v11f1e_sd15_tile", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.bin", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "tile", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "OpenPose", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_openpose", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "openpose", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "LineArt", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_lineart", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "lineart", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "LineArt Anime", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15s2_lineart_anime", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "lineart_anime", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "NormalBae", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_normalbae", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "normalbae", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Seg", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_seg", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "seg", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Soft Edge", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_softedge", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "softedge", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Scribble", |
||||
"RepositoryPath": "lllyasviel/control_v11p_sd15_scribble", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "scribble", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersControlNet", |
||||
"ModelName": "Shuffle", |
||||
"RepositoryPath": "lllyasviel/control_v11e_sd15_shuffle", |
||||
"Files": [ |
||||
"diffusion_pytorch_model.safetensors", |
||||
"config.json" |
||||
], |
||||
"Subfolder": "shuffle", |
||||
"LicenseType": "Open RAIL" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersClipVision", |
||||
"ModelName": "IP Adapter Encoder", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_sd_image_encoder", |
||||
"Files": [ |
||||
"config.json", |
||||
"model.safetensors" |
||||
], |
||||
"Subfolder": "ip_adapter_sd_image_encoder", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersClipVision", |
||||
"ModelName": "IP Adapter Encoder (SDXL)", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_sdxl_image_encoder", |
||||
"Files": [ |
||||
"config.json", |
||||
"model.safetensors" |
||||
], |
||||
"Subfolder": "ip_adapter_sd_image_encoder", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapter", |
||||
"ModelName": "SD 1.5 Adapter", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_sd15", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_sd15", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapter", |
||||
"ModelName": "SD 1.5 Light Adapter", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_sd15_light", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_sd15_light", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapter", |
||||
"ModelName": "SD 1.5 Plus Adapter", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_plus_sd15", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_plus_sd15", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapter", |
||||
"ModelName": "SD 1.5 Face Plus Adapter", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_plus_face_sd15", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_plus_face_sd15", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapterXl", |
||||
"ModelName": "SDXL Adapter", |
||||
"RepositoryPath": "InvokeAI/ip_adapter_sdxl", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_sdxl", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapterXl", |
||||
"ModelName": "SDXL Plus Adapter", |
||||
"RepositoryPath": "InvokeAI/ip-adapter-plus_sdxl_vit-h", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_plus_sdxl_vit-h", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersIpAdapterXl", |
||||
"ModelName": "SDXL Face Plus Adapter", |
||||
"RepositoryPath": "InvokeAI/ip-adapter-plus-face_sdxl_vit-h", |
||||
"Files": [ |
||||
"image_encoder.txt", |
||||
"ip_adapter.bin" |
||||
], |
||||
"Subfolder": "ip_adapter_plus_face_sdxl_vit-h", |
||||
"LicenseType": "CreativeML Open RAIL-M" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Sketch", |
||||
"RepositoryPath": "TencentARC/t2iadapter_sketch_sd15v2", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.bin" |
||||
], |
||||
"Subfolder": "t2iadapter_sketch_sd15v2", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Depth", |
||||
"RepositoryPath": "TencentARC/t2iadapter_depth_sd15v2", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.bin" |
||||
], |
||||
"Subfolder": "t2iadapter_depth_sd15v2", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Canny", |
||||
"RepositoryPath": "TencentARC/t2iadapter_canny_sd15v2", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.bin" |
||||
], |
||||
"Subfolder": "t2iadapter_canny_sd15v2", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Depth-Zoe", |
||||
"RepositoryPath": "TencentARC/t2iadapter_zoedepth_sd15v1", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.bin" |
||||
], |
||||
"Subfolder": "t2iadapter_zoedepth_sd15v1", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Sketch (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-sketch-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-sketch-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Depth-Zoe (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-depth-zoe-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-depth-zoe-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "OpenPose (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-openpose-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-openpose-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Depth-MiDaS (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-depth-midas-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-depth-midas-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "LineArt (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-lineart-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-lineart-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
}, |
||||
{ |
||||
"ModelCategory": "DiffusersT2IAdapter", |
||||
"ModelName": "Canny (SDXL)", |
||||
"RepositoryPath": "TencentARC/t2i-adapter-canny-sdxl-1.0", |
||||
"Files": [ |
||||
"config.json", |
||||
"diffusion_pytorch_model.safetensors" |
||||
], |
||||
"Subfolder": "t2i-adapter-canny-sdxl-1.0", |
||||
"LicenseType": "Apache 2.0" |
||||
} |
||||
] |
@ -0,0 +1,30 @@
|
||||
using System; |
||||
using System.Threading.Tasks; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
using StabilityMatrix.Core.Services; |
||||
|
||||
namespace StabilityMatrix.Avalonia.DesignData; |
||||
|
||||
public class MockMetadataImportService : IMetadataImportService |
||||
{ |
||||
public Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
|
||||
public Task<ConnectedModelInfo?> GetMetadataForFile( |
||||
FilePath filePath, |
||||
IProgress<ProgressReport>? progress = null, |
||||
bool forceReimport = false |
||||
) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
return Task.CompletedTask; |
||||
} |
||||
} |
@ -0,0 +1,46 @@
|
||||
using System.Text.Json.Serialization; |
||||
using StabilityMatrix.Core.Converters.Json; |
||||
using StabilityMatrix.Core.Extensions; |
||||
using StabilityMatrix.Core.Models; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Models.HuggingFace; |
||||
|
||||
[JsonConverter(typeof(DefaultUnknownEnumConverter<HuggingFaceModelType>))] |
||||
public enum HuggingFaceModelType |
||||
{ |
||||
[Description("Base Models")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.StableDiffusion)] |
||||
BaseModel, |
||||
|
||||
[Description("ControlNets")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)] |
||||
ControlNet, |
||||
|
||||
[Description("ControlNets (Diffusers)")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.ControlNet)] |
||||
DiffusersControlNet, |
||||
|
||||
[Description("IP Adapters")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.IpAdapter)] |
||||
IpAdapter, |
||||
|
||||
[Description("IP Adapters (Diffusers SD1.5)")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.InvokeIpAdapters15)] |
||||
DiffusersIpAdapter, |
||||
|
||||
[Description("IP Adapters (Diffusers SDXL)")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.InvokeIpAdaptersXl)] |
||||
DiffusersIpAdapterXl, |
||||
|
||||
[Description("CLIP Vision (Diffusers)")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.InvokeClipVision)] |
||||
DiffusersClipVision, |
||||
|
||||
[Description("T2I Adapters")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.T2IAdapter)] |
||||
T2IAdapter, |
||||
|
||||
[Description("T2I Adapters (Diffusers)")] |
||||
[ConvertTo<SharedFolderType>(SharedFolderType.T2IAdapter)] |
||||
DiffusersT2IAdapter, |
||||
} |
@ -0,0 +1,12 @@
|
||||
namespace StabilityMatrix.Avalonia.Models.HuggingFace; |
||||
|
||||
public class HuggingfaceItem |
||||
{ |
||||
public required HuggingFaceModelType ModelCategory { get; set; } |
||||
public required string ModelName { get; set; } |
||||
public required string RepositoryPath { get; set; } |
||||
public required string[] Files { get; set; } |
||||
public required string LicenseType { get; set; } |
||||
public string? LicensePath { get; set; } |
||||
public string? Subfolder { get; set; } |
||||
} |
@ -0,0 +1,6 @@
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Base; |
||||
|
||||
public abstract class TabViewModelBase : ViewModelBase |
||||
{ |
||||
public abstract string Header { get; } |
||||
} |
@ -0,0 +1,612 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.ComponentModel; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Net.Http; |
||||
using System.Reactive; |
||||
using System.Reactive.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using AsyncAwaitBestPractices; |
||||
using Avalonia.Collections; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Controls.Notifications; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using LiteDB; |
||||
using LiteDB.Async; |
||||
using NLog; |
||||
using Refit; |
||||
using StabilityMatrix.Avalonia.Languages; |
||||
using StabilityMatrix.Avalonia.Services; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; |
||||
using StabilityMatrix.Avalonia.Views; |
||||
using StabilityMatrix.Core.Api; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Database; |
||||
using StabilityMatrix.Core.Extensions; |
||||
using StabilityMatrix.Core.Helper; |
||||
using StabilityMatrix.Core.Helper.Cache; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.Api; |
||||
using StabilityMatrix.Core.Models.Settings; |
||||
using StabilityMatrix.Core.Services; |
||||
using Notification = Avalonia.Controls.Notifications.Notification; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; |
||||
|
||||
[View(typeof(CivitAiBrowserPage))] |
||||
[Singleton] |
||||
public partial class CivitAiBrowserViewModel : TabViewModelBase |
||||
{ |
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||
private readonly ICivitApi civitApi; |
||||
private readonly IDownloadService downloadService; |
||||
private readonly ISettingsManager settingsManager; |
||||
private readonly ServiceManager<ViewModelBase> dialogFactory; |
||||
private readonly ILiteDbContext liteDbContext; |
||||
private readonly INotificationService notificationService; |
||||
private const int MaxModelsPerPage = 20; |
||||
private LRUCache< |
||||
int /* model id */ |
||||
, |
||||
CheckpointBrowserCardViewModel |
||||
> cache = new(50); |
||||
|
||||
[ObservableProperty] |
||||
private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards; |
||||
|
||||
[ObservableProperty] |
||||
private DataGridCollectionView? modelCardsView; |
||||
|
||||
[ObservableProperty] |
||||
private string searchQuery = string.Empty; |
||||
|
||||
[ObservableProperty] |
||||
private bool showNsfw; |
||||
|
||||
[ObservableProperty] |
||||
private bool showMainLoadingSpinner; |
||||
|
||||
[ObservableProperty] |
||||
private CivitPeriod selectedPeriod = CivitPeriod.Month; |
||||
|
||||
[ObservableProperty] |
||||
private CivitSortMode sortMode = CivitSortMode.HighestRated; |
||||
|
||||
[ObservableProperty] |
||||
private CivitModelType selectedModelType = CivitModelType.Checkpoint; |
||||
|
||||
[ObservableProperty] |
||||
private int currentPageNumber; |
||||
|
||||
[ObservableProperty] |
||||
private int totalPages; |
||||
|
||||
[ObservableProperty] |
||||
private bool hasSearched; |
||||
|
||||
[ObservableProperty] |
||||
private bool canGoToNextPage; |
||||
|
||||
[ObservableProperty] |
||||
private bool canGoToPreviousPage; |
||||
|
||||
[ObservableProperty] |
||||
private bool canGoToFirstPage; |
||||
|
||||
[ObservableProperty] |
||||
private bool canGoToLastPage; |
||||
|
||||
[ObservableProperty] |
||||
private bool isIndeterminate; |
||||
|
||||
[ObservableProperty] |
||||
private bool noResultsFound; |
||||
|
||||
[ObservableProperty] |
||||
private string noResultsText = string.Empty; |
||||
|
||||
[ObservableProperty] |
||||
private string selectedBaseModelType = "All"; |
||||
|
||||
private List<CheckpointBrowserCardViewModel> allModelCards = new(); |
||||
|
||||
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.All || t.ConvertTo<SharedFolderType>() > 0) |
||||
.OrderBy(t => t.ToString()); |
||||
|
||||
public List<string> BaseModelOptions => new() { "All", "SD 1.5", "SD 2.1", "SDXL 0.9", "SDXL 1.0" }; |
||||
|
||||
public CivitAiBrowserViewModel( |
||||
ICivitApi civitApi, |
||||
IDownloadService downloadService, |
||||
ISettingsManager settingsManager, |
||||
ServiceManager<ViewModelBase> dialogFactory, |
||||
ILiteDbContext liteDbContext, |
||||
INotificationService notificationService |
||||
) |
||||
{ |
||||
this.civitApi = civitApi; |
||||
this.downloadService = downloadService; |
||||
this.settingsManager = settingsManager; |
||||
this.dialogFactory = dialogFactory; |
||||
this.liteDbContext = liteDbContext; |
||||
this.notificationService = notificationService; |
||||
|
||||
CurrentPageNumber = 1; |
||||
CanGoToNextPage = true; |
||||
CanGoToLastPage = true; |
||||
|
||||
Observable |
||||
.FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged)) |
||||
.Where(x => x.EventArgs.PropertyName == nameof(CurrentPageNumber)) |
||||
.Throttle(TimeSpan.FromMilliseconds(250)) |
||||
.Select<EventPattern<PropertyChangedEventArgs>, int>(_ => CurrentPageNumber) |
||||
.Where(page => page <= TotalPages && page > 0) |
||||
.ObserveOn(SynchronizationContext.Current) |
||||
.Subscribe(_ => TrySearchAgain(false).SafeFireAndForget(), err => Logger.Error(err)); |
||||
} |
||||
|
||||
public override void OnLoaded() |
||||
{ |
||||
if (Design.IsDesignMode) |
||||
return; |
||||
|
||||
var searchOptions = settingsManager.Settings.ModelSearchOptions; |
||||
|
||||
// Fix SelectedModelType if someone had selected the obsolete "Model" option |
||||
if (searchOptions is { SelectedModelType: CivitModelType.Model }) |
||||
{ |
||||
settingsManager.Transaction( |
||||
s => |
||||
s.ModelSearchOptions = new ModelSearchOptions( |
||||
SelectedPeriod, |
||||
SortMode, |
||||
CivitModelType.Checkpoint, |
||||
SelectedBaseModelType |
||||
) |
||||
); |
||||
searchOptions = settingsManager.Settings.ModelSearchOptions; |
||||
} |
||||
|
||||
SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.Month; |
||||
SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated; |
||||
SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint; |
||||
SelectedBaseModelType = searchOptions?.SelectedBaseModelType ?? "All"; |
||||
|
||||
ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled; |
||||
|
||||
settingsManager.RelayPropertyFor(this, model => model.ShowNsfw, settings => settings.ModelBrowserNsfwEnabled); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Filter predicate for model cards |
||||
/// </summary> |
||||
private bool FilterModelCardsPredicate(object? item) |
||||
{ |
||||
if (item is not CheckpointBrowserCardViewModel card) |
||||
return false; |
||||
return !card.CivitModel.Nsfw || ShowNsfw; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Background update task |
||||
/// </summary> |
||||
private async Task CivitModelQuery(CivitModelsRequest request) |
||||
{ |
||||
var timer = Stopwatch.StartNew(); |
||||
var queryText = request.Query; |
||||
try |
||||
{ |
||||
var modelsResponse = await civitApi.GetModels(request); |
||||
var models = modelsResponse.Items; |
||||
if (models is null) |
||||
{ |
||||
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 |
||||
); |
||||
|
||||
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 and archived/taken-down models |
||||
models = models.Where(m => m.Type.ConvertTo<SharedFolderType>() > 0).Where(m => m.Mode == null).ToList(); |
||||
|
||||
// Database update calls will invoke `OnModelsUpdated` |
||||
// Add to database |
||||
await liteDbContext.UpsertCivitModelAsync(models); |
||||
// Add as cache entry |
||||
var cacheNew = await liteDbContext.UpsertCivitModelQueryCacheEntryAsync( |
||||
new() |
||||
{ |
||||
Id = ObjectHash.GetMd5Guid(request), |
||||
InsertedAt = DateTimeOffset.UtcNow, |
||||
Request = request, |
||||
Items = models, |
||||
Metadata = modelsResponse.Metadata |
||||
} |
||||
); |
||||
|
||||
if (cacheNew) |
||||
{ |
||||
Logger.Debug("New cache entry, updating model cards"); |
||||
UpdateModelCards(models, modelsResponse.Metadata); |
||||
} |
||||
else |
||||
{ |
||||
Logger.Debug("Cache entry already exists, not updating model cards"); |
||||
} |
||||
} |
||||
catch (OperationCanceledException) |
||||
{ |
||||
notificationService.Show( |
||||
new Notification("Request to CivitAI timed out", "Please try again in a few minutes") |
||||
); |
||||
Logger.Warn($"CivitAI query timed out ({request})"); |
||||
} |
||||
catch (HttpRequestException e) |
||||
{ |
||||
notificationService.Show( |
||||
new Notification("CivitAI can't be reached right now", "Please try again in a few minutes") |
||||
); |
||||
Logger.Warn(e, $"CivitAI query HttpRequestException ({request})"); |
||||
} |
||||
catch (ApiException e) |
||||
{ |
||||
notificationService.Show( |
||||
new Notification("CivitAI can't be reached right now", "Please try again in a few minutes") |
||||
); |
||||
Logger.Warn(e, $"CivitAI query ApiException ({request})"); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
notificationService.Show( |
||||
new Notification( |
||||
"CivitAI can't be reached right now", |
||||
$"Unknown exception during CivitAI query: {e.GetType().Name}" |
||||
) |
||||
); |
||||
Logger.Error(e, $"CivitAI query unknown exception ({request})"); |
||||
} |
||||
finally |
||||
{ |
||||
ShowMainLoadingSpinner = false; |
||||
UpdateResultsText(); |
||||
} |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Updates model cards using api response object. |
||||
/// </summary> |
||||
private void UpdateModelCards(IEnumerable<CivitModel>? models, CivitMetadata? metadata) |
||||
{ |
||||
if (models is null) |
||||
{ |
||||
ModelCards?.Clear(); |
||||
} |
||||
else |
||||
{ |
||||
var updateCards = models |
||||
.Select(model => |
||||
{ |
||||
var cachedViewModel = cache.Get(model.Id); |
||||
if (cachedViewModel != null) |
||||
{ |
||||
if (!cachedViewModel.IsImporting) |
||||
{ |
||||
cache.Remove(model.Id); |
||||
} |
||||
|
||||
return cachedViewModel; |
||||
} |
||||
|
||||
var newCard = dialogFactory.Get<CheckpointBrowserCardViewModel>(vm => |
||||
{ |
||||
vm.CivitModel = model; |
||||
vm.OnDownloadStart = viewModel => |
||||
{ |
||||
if (cache.Get(viewModel.CivitModel.Id) != null) |
||||
return; |
||||
cache.Add(viewModel.CivitModel.Id, viewModel); |
||||
}; |
||||
|
||||
return vm; |
||||
}); |
||||
|
||||
return newCard; |
||||
}) |
||||
.ToList(); |
||||
|
||||
allModelCards = updateCards; |
||||
|
||||
var filteredCards = updateCards.Where(FilterModelCardsPredicate); |
||||
if (SortMode == CivitSortMode.Installed) |
||||
{ |
||||
filteredCards = filteredCards.OrderByDescending(x => x.UpdateCardText == "Update Available"); |
||||
} |
||||
|
||||
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(filteredCards); |
||||
} |
||||
TotalPages = metadata?.TotalPages ?? 1; |
||||
CanGoToFirstPage = CurrentPageNumber != 1; |
||||
CanGoToPreviousPage = CurrentPageNumber > 1; |
||||
CanGoToNextPage = CurrentPageNumber < TotalPages; |
||||
CanGoToLastPage = CurrentPageNumber != TotalPages; |
||||
// Status update |
||||
ShowMainLoadingSpinner = false; |
||||
IsIndeterminate = false; |
||||
HasSearched = true; |
||||
} |
||||
|
||||
private string previousSearchQuery = string.Empty; |
||||
|
||||
[RelayCommand] |
||||
private async Task SearchModels() |
||||
{ |
||||
var timer = Stopwatch.StartNew(); |
||||
|
||||
if (SearchQuery != previousSearchQuery) |
||||
{ |
||||
// Reset page number |
||||
CurrentPageNumber = 1; |
||||
previousSearchQuery = SearchQuery; |
||||
} |
||||
|
||||
// Build request |
||||
var modelRequest = new CivitModelsRequest |
||||
{ |
||||
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) |
||||
{ |
||||
modelRequest.Types = new[] { SelectedModelType }; |
||||
} |
||||
|
||||
if (SelectedBaseModelType != "All") |
||||
{ |
||||
modelRequest.BaseModel = SelectedBaseModelType; |
||||
} |
||||
|
||||
if (SortMode == CivitSortMode.Installed) |
||||
{ |
||||
var connectedModels = CheckpointFile |
||||
.GetAllCheckpointFiles(settingsManager.ModelsDirectory) |
||||
.Where(c => c.IsConnectedModel); |
||||
|
||||
modelRequest.CommaSeparatedModelIds = string.Join( |
||||
",", |
||||
connectedModels.Select(c => c.ConnectedModel!.ModelId).GroupBy(m => m).Select(g => g.First()) |
||||
); |
||||
modelRequest.Sort = null; |
||||
modelRequest.Period = null; |
||||
} |
||||
else if (SortMode == CivitSortMode.Favorites) |
||||
{ |
||||
var favoriteModels = settingsManager.Settings.FavoriteModels; |
||||
|
||||
if (!favoriteModels.Any()) |
||||
{ |
||||
notificationService.Show( |
||||
"No Favorites", |
||||
"You have not added any models to your Favorites.", |
||||
NotificationType.Error |
||||
); |
||||
return; |
||||
} |
||||
|
||||
modelRequest.CommaSeparatedModelIds = string.Join(",", favoriteModels); |
||||
modelRequest.Sort = null; |
||||
modelRequest.Period = null; |
||||
} |
||||
|
||||
// See if query is cached |
||||
CivitModelQueryCacheEntry? cachedQuery = null; |
||||
|
||||
try |
||||
{ |
||||
cachedQuery = await liteDbContext |
||||
.CivitModelQueryCache |
||||
.IncludeAll() |
||||
.FindByIdAsync(ObjectHash.GetMd5Guid(modelRequest)); |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
// Suppress 'Training_Data' enum not found exceptions |
||||
// Caused by enum name change |
||||
// Ignore to do a new search to overwrite the cache |
||||
if ( |
||||
!( |
||||
e is LiteException or LiteAsyncException |
||||
&& e.InnerException is ArgumentException inner |
||||
&& inner.Message.Contains("Training_Data") |
||||
) |
||||
) |
||||
{ |
||||
// Otherwise log error |
||||
Logger.Error(e, "Error while querying CivitModelQueryCache"); |
||||
} |
||||
} |
||||
|
||||
// If cached, update model cards |
||||
if (cachedQuery is not null) |
||||
{ |
||||
var elapsed = timer.Elapsed; |
||||
Logger.Debug( |
||||
"Using cached query for {Text} [{RequestHash}] (in {Elapsed:F1} s)", |
||||
SearchQuery, |
||||
modelRequest.GetHashCode(), |
||||
elapsed.TotalSeconds |
||||
); |
||||
UpdateModelCards(cachedQuery.Items, cachedQuery.Metadata); |
||||
|
||||
// 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) |
||||
{ |
||||
CivitModelQuery(modelRequest).SafeFireAndForget(); |
||||
Logger.Debug( |
||||
"Cached query was more than 2 minutes ago ({Seconds:F0} s), updating cache with remote query", |
||||
timeSinceCache.Value.TotalSeconds |
||||
); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// Not cached, wait for remote query |
||||
ShowMainLoadingSpinner = true; |
||||
await CivitModelQuery(modelRequest); |
||||
} |
||||
|
||||
UpdateResultsText(); |
||||
} |
||||
|
||||
public void FirstPage() |
||||
{ |
||||
CurrentPageNumber = 1; |
||||
} |
||||
|
||||
public void PreviousPage() |
||||
{ |
||||
if (CurrentPageNumber == 1) |
||||
return; |
||||
|
||||
CurrentPageNumber--; |
||||
} |
||||
|
||||
public void NextPage() |
||||
{ |
||||
if (CurrentPageNumber == TotalPages) |
||||
return; |
||||
|
||||
CurrentPageNumber++; |
||||
} |
||||
|
||||
public void LastPage() |
||||
{ |
||||
CurrentPageNumber = TotalPages; |
||||
} |
||||
|
||||
public void ClearSearchQuery() |
||||
{ |
||||
SearchQuery = string.Empty; |
||||
} |
||||
|
||||
partial void OnShowNsfwChanged(bool value) |
||||
{ |
||||
settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled, value); |
||||
// ModelCardsView?.Refresh(); |
||||
var updateCards = allModelCards.Where(FilterModelCardsPredicate); |
||||
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(updateCards); |
||||
|
||||
if (!HasSearched) |
||||
return; |
||||
|
||||
UpdateResultsText(); |
||||
} |
||||
|
||||
partial void OnSelectedPeriodChanged(CivitPeriod value) |
||||
{ |
||||
TrySearchAgain().SafeFireAndForget(); |
||||
settingsManager.Transaction( |
||||
s => |
||||
s.ModelSearchOptions = new ModelSearchOptions(value, SortMode, SelectedModelType, SelectedBaseModelType) |
||||
); |
||||
} |
||||
|
||||
partial void OnSortModeChanged(CivitSortMode value) |
||||
{ |
||||
TrySearchAgain().SafeFireAndForget(); |
||||
settingsManager.Transaction( |
||||
s => |
||||
s.ModelSearchOptions = new ModelSearchOptions( |
||||
SelectedPeriod, |
||||
value, |
||||
SelectedModelType, |
||||
SelectedBaseModelType |
||||
) |
||||
); |
||||
} |
||||
|
||||
partial void OnSelectedModelTypeChanged(CivitModelType value) |
||||
{ |
||||
TrySearchAgain().SafeFireAndForget(); |
||||
settingsManager.Transaction( |
||||
s => s.ModelSearchOptions = new ModelSearchOptions(SelectedPeriod, SortMode, value, SelectedBaseModelType) |
||||
); |
||||
} |
||||
|
||||
partial void OnSelectedBaseModelTypeChanged(string value) |
||||
{ |
||||
TrySearchAgain().SafeFireAndForget(); |
||||
settingsManager.Transaction( |
||||
s => s.ModelSearchOptions = new ModelSearchOptions(SelectedPeriod, SortMode, SelectedModelType, value) |
||||
); |
||||
} |
||||
|
||||
private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) |
||||
{ |
||||
if (!HasSearched) |
||||
return; |
||||
ModelCards?.Clear(); |
||||
|
||||
if (shouldUpdatePageNumber) |
||||
{ |
||||
CurrentPageNumber = 1; |
||||
} |
||||
|
||||
// execute command instead of calling method directly so that the IsRunning property gets updated |
||||
await SearchModelsCommand.ExecuteAsync(null); |
||||
} |
||||
|
||||
private void UpdateResultsText() |
||||
{ |
||||
NoResultsFound = ModelCards?.Count <= 0; |
||||
NoResultsText = |
||||
allModelCards.Count > 0 ? $"{allModelCards.Count} results hidden by filters" : "No results found"; |
||||
} |
||||
|
||||
public override string Header => Resources.Label_CivitAi; |
||||
} |
@ -0,0 +1,193 @@
|
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Reactive.Linq; |
||||
using System.Text.Json; |
||||
using System.Text.Json.Serialization; |
||||
using System.Threading.Tasks; |
||||
using Avalonia; |
||||
using Avalonia.Controls.Notifications; |
||||
using Avalonia.Data; |
||||
using Avalonia.Input; |
||||
using Avalonia.Markup.Xaml.MarkupExtensions; |
||||
using Avalonia.Media; |
||||
using Avalonia.Threading; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using DynamicData; |
||||
using DynamicData.Binding; |
||||
using FluentAvalonia.UI.Controls; |
||||
using StabilityMatrix.Avalonia.Languages; |
||||
using StabilityMatrix.Avalonia.Models.HuggingFace; |
||||
using StabilityMatrix.Avalonia.Services; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.ViewModels.HuggingFacePage; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Extensions; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
using StabilityMatrix.Core.Services; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; |
||||
|
||||
[View(typeof(Views.HuggingFacePage))] |
||||
[Singleton] |
||||
public partial class HuggingFacePageViewModel : TabViewModelBase |
||||
{ |
||||
private readonly ITrackedDownloadService trackedDownloadService; |
||||
private readonly ISettingsManager settingsManager; |
||||
private readonly INotificationService notificationService; |
||||
|
||||
public SourceCache<HuggingfaceItem, string> ItemsCache { get; } = new(i => i.RepositoryPath + i.ModelName); |
||||
|
||||
public IObservableCollection<CategoryViewModel> Categories { get; set; } = |
||||
new ObservableCollectionExtended<CategoryViewModel>(); |
||||
|
||||
public string DownloadPercentText => |
||||
Math.Abs(TotalProgress.Percentage - 100f) < 0.001f |
||||
? "Download Complete" |
||||
: $"Downloading {TotalProgress.Percentage:0}%"; |
||||
|
||||
[ObservableProperty] |
||||
private int numSelected; |
||||
|
||||
private ConcurrentDictionary<Guid, ProgressReport> progressReports = new(); |
||||
|
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(DownloadPercentText))] |
||||
private ProgressReport totalProgress; |
||||
|
||||
private readonly DispatcherTimer progressTimer = new() { Interval = TimeSpan.FromMilliseconds(100) }; |
||||
|
||||
public HuggingFacePageViewModel( |
||||
ITrackedDownloadService trackedDownloadService, |
||||
ISettingsManager settingsManager, |
||||
INotificationService notificationService |
||||
) |
||||
{ |
||||
this.trackedDownloadService = trackedDownloadService; |
||||
this.settingsManager = settingsManager; |
||||
this.notificationService = notificationService; |
||||
|
||||
ItemsCache |
||||
.Connect() |
||||
.DeferUntilLoaded() |
||||
.Group(i => i.ModelCategory) |
||||
.Transform(g => new CategoryViewModel(g.Cache.Items) { Title = g.Key.GetDescription() ?? g.Key.ToString() }) |
||||
.SortBy(vm => vm.Title) |
||||
.Bind(Categories) |
||||
.WhenAnyPropertyChanged() |
||||
.Subscribe(_ => NumSelected = Categories.Sum(c => c.NumSelected)); |
||||
|
||||
progressTimer.Tick += (_, _) => |
||||
{ |
||||
var currentSum = 0ul; |
||||
var totalSum = 0ul; |
||||
foreach (var progress in progressReports.Values) |
||||
{ |
||||
currentSum += progress.Current ?? 0; |
||||
totalSum += progress.Total ?? 0; |
||||
} |
||||
|
||||
TotalProgress = new ProgressReport(current: currentSum, total: totalSum); |
||||
}; |
||||
} |
||||
|
||||
public override void OnLoaded() |
||||
{ |
||||
if (ItemsCache.Count > 0) |
||||
return; |
||||
|
||||
using var reader = new StreamReader(Assets.HfPackagesJson.Open()); |
||||
var packages = |
||||
JsonSerializer.Deserialize<IReadOnlyList<HuggingfaceItem>>( |
||||
reader.ReadToEnd(), |
||||
new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } } |
||||
) ?? throw new InvalidOperationException("Failed to read hf-packages.json"); |
||||
|
||||
ItemsCache.EditDiff(packages, (a, b) => a.RepositoryPath == b.RepositoryPath); |
||||
} |
||||
|
||||
public void ClearSelection() |
||||
{ |
||||
foreach (var category in Categories) |
||||
{ |
||||
category.IsChecked = true; |
||||
category.IsChecked = false; |
||||
} |
||||
} |
||||
|
||||
public void SelectAll() |
||||
{ |
||||
foreach (var category in Categories) |
||||
{ |
||||
category.IsChecked = true; |
||||
} |
||||
} |
||||
|
||||
[RelayCommand] |
||||
private async Task ImportSelected() |
||||
{ |
||||
var selected = Categories.SelectMany(c => c.Items).Where(i => i.IsSelected).ToArray(); |
||||
|
||||
foreach (var viewModel in selected) |
||||
{ |
||||
foreach (var file in viewModel.Item.Files) |
||||
{ |
||||
var url = $"https://huggingface.co/{viewModel.Item.RepositoryPath}/resolve/main/{file}?download=true"; |
||||
var sharedFolderType = viewModel.Item.ModelCategory.ConvertTo<SharedFolderType>(); |
||||
var downloadPath = new FilePath( |
||||
Path.Combine( |
||||
settingsManager.ModelsDirectory, |
||||
sharedFolderType.GetDescription() ?? sharedFolderType.ToString(), |
||||
viewModel.Item.Subfolder ?? string.Empty, |
||||
file |
||||
) |
||||
); |
||||
Directory.CreateDirectory(downloadPath.Directory); |
||||
var download = trackedDownloadService.NewDownload(url, downloadPath); |
||||
download.ProgressUpdate += DownloadOnProgressUpdate; |
||||
download.Start(); |
||||
|
||||
await Task.Delay(Random.Shared.Next(50, 100)); |
||||
} |
||||
} |
||||
progressTimer.Start(); |
||||
} |
||||
|
||||
private void DownloadOnProgressUpdate(object? sender, ProgressReport e) |
||||
{ |
||||
if (sender is not TrackedDownload trackedDownload) |
||||
return; |
||||
|
||||
progressReports[trackedDownload.Id] = e; |
||||
} |
||||
|
||||
partial void OnTotalProgressChanged(ProgressReport value) |
||||
{ |
||||
if (Math.Abs(value.Percentage - 100) < 0.001f) |
||||
{ |
||||
notificationService.Show( |
||||
"Download complete", |
||||
"All selected models have been downloaded.", |
||||
NotificationType.Success |
||||
); |
||||
progressTimer.Stop(); |
||||
DelayedClearProgress(TimeSpan.FromSeconds(1.5)); |
||||
} |
||||
} |
||||
|
||||
private void DelayedClearProgress(TimeSpan delay) |
||||
{ |
||||
Task.Delay(delay) |
||||
.ContinueWith(_ => |
||||
{ |
||||
TotalProgress = new ProgressReport(0, 0); |
||||
}); |
||||
} |
||||
|
||||
public override string Header => Resources.Label_HuggingFace; |
||||
} |
@ -0,0 +1,52 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using DynamicData; |
||||
using DynamicData.Binding; |
||||
using StabilityMatrix.Avalonia.Models.HuggingFace; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.HuggingFacePage; |
||||
|
||||
public partial class CategoryViewModel : ViewModelBase |
||||
{ |
||||
[ObservableProperty] |
||||
private IObservableCollection<HuggingfaceItemViewModel> items = |
||||
new ObservableCollectionExtended<HuggingfaceItemViewModel>(); |
||||
|
||||
public SourceCache<HuggingfaceItem, string> ItemsCache { get; } = new(i => i.RepositoryPath + i.ModelName); |
||||
|
||||
[ObservableProperty] |
||||
private string? title; |
||||
|
||||
[ObservableProperty] |
||||
private bool isChecked; |
||||
|
||||
[ObservableProperty] |
||||
private int numSelected; |
||||
|
||||
public CategoryViewModel(IEnumerable<HuggingfaceItem> items) |
||||
{ |
||||
ItemsCache |
||||
.Connect() |
||||
.DeferUntilLoaded() |
||||
.Transform(i => new HuggingfaceItemViewModel { Item = i }) |
||||
.Bind(Items) |
||||
.WhenPropertyChanged(p => p.IsSelected) |
||||
.Subscribe(_ => NumSelected = Items.Count(i => i.IsSelected)); |
||||
|
||||
ItemsCache.EditDiff(items, (a, b) => a.RepositoryPath == b.RepositoryPath); |
||||
} |
||||
|
||||
partial void OnIsCheckedChanged(bool value) |
||||
{ |
||||
if (Items is null) |
||||
return; |
||||
|
||||
foreach (var item in Items) |
||||
{ |
||||
item.IsSelected = value; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,25 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using StabilityMatrix.Avalonia.Models.HuggingFace; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.HuggingFacePage; |
||||
|
||||
public partial class HuggingfaceItemViewModel : ViewModelBase |
||||
{ |
||||
[ObservableProperty] |
||||
private HuggingfaceItem item; |
||||
|
||||
[ObservableProperty] |
||||
private bool isSelected; |
||||
|
||||
public string LicenseUrl => |
||||
$"https://huggingface.co/{Item.RepositoryPath}/blob/main/{Item.LicensePath ?? "README.md"}"; |
||||
public string RepoUrl => $"https://huggingface.co/{Item.RepositoryPath}"; |
||||
|
||||
[RelayCommand] |
||||
private void ToggleSelected() |
||||
{ |
||||
IsSelected = !IsSelected; |
||||
} |
||||
} |
@ -1,531 +1,18 @@
|
||||
<UserControl |
||||
x:Class="StabilityMatrix.Avalonia.Views.CheckpointBrowserPage" |
||||
xmlns="https://github.com/avaloniaui" |
||||
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" |
||||
xmlns:checkpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager" |
||||
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" |
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" |
||||
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
d:DataContext="{x:Static designData:DesignData.CheckpointBrowserViewModel}" |
||||
d:DesignHeight="700" |
||||
d:DesignWidth="800" |
||||
x:CompileBindings="True" |
||||
x:DataType="viewModels:CheckpointBrowserViewModel" |
||||
mc:Ignorable="d"> |
||||
|
||||
<UserControl.Styles> |
||||
<Style Selector="Border#HoverBorder"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237"/> |
||||
</Transitions> |
||||
</Setter> |
||||
|
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<TransformOperationsTransition Property="RenderTransform" |
||||
Duration="0:0:0.237"> |
||||
<TransformOperationsTransition.Easing> |
||||
<QuadraticEaseInOut/> |
||||
</TransformOperationsTransition.Easing> |
||||
</TransformOperationsTransition> |
||||
</Transitions> |
||||
</Setter> |
||||
</Style> |
||||
|
||||
<Style Selector="^:pointerover"> |
||||
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000"/> |
||||
<Setter Property="Cursor" Value="Hand" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="12"/> |
||||
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)"/> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#CC000000" /> |
||||
</Style> |
||||
</Style> |
||||
|
||||
<Style Selector="^:not(:pointerover)"> |
||||
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000"/> |
||||
<Setter Property="Cursor" Value="Arrow" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="8"/> |
||||
<Setter Property="RenderTransform" Value="scale(1, 1)"/> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#99000000" /> |
||||
</Style> |
||||
</Style> |
||||
</Style> |
||||
</UserControl.Styles> |
||||
|
||||
<UserControl.Resources> |
||||
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter"/> |
||||
<DataTemplate x:Key="CivitModelTemplate" DataType="{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}"> |
||||
<Border |
||||
Name="HoverBorder" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
Margin="8" |
||||
ClipToBounds="True" |
||||
CornerRadius="8"> |
||||
<Border.ContextFlyout> |
||||
<MenuFlyout> |
||||
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnCivitAi}" |
||||
Command="{Binding OpenModelCommand}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Border.ContextFlyout> |
||||
<Button |
||||
Name="ModelCard" |
||||
Classes="transparent-full" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
VerticalContentAlignment="Top" |
||||
CornerRadius="8" |
||||
Command="{Binding ShowVersionDialogCommand}" |
||||
CommandParameter="{Binding CivitModel}" |
||||
IsEnabled="{Binding !IsImporting}"> |
||||
<Grid RowDefinitions="*, Auto"> |
||||
<controls:BetterAdvancedImage |
||||
Grid.RowSpan="2" |
||||
CornerRadius="8" |
||||
Width="330" |
||||
Height="400" |
||||
Source="{Binding CardImage}" |
||||
Stretch="UniformToFill" |
||||
StretchDirection="Both"/> |
||||
|
||||
<StackPanel |
||||
Grid.Row="0" |
||||
HorizontalAlignment="Right" |
||||
Orientation="Horizontal"> |
||||
<Button |
||||
Margin="0,8,8,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
Classes="transparent-info" |
||||
Command="{Binding ToggleFavoriteCommand}" |
||||
FontSize="20" |
||||
IsVisible="{Binding !IsFavorite}"> |
||||
<Grid> |
||||
<ui:SymbolIcon Symbol="StarAdd" /> |
||||
</Grid> |
||||
</Button> |
||||
<Button |
||||
Margin="0,8,8,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
Classes="success" |
||||
Command="{Binding ToggleFavoriteCommand}" |
||||
FontSize="20" |
||||
IsVisible="{Binding IsFavorite}"> |
||||
<Grid> |
||||
<ui:SymbolIcon Symbol="StarFilled" /> |
||||
</Grid> |
||||
</Button> |
||||
</StackPanel> |
||||
|
||||
<!-- Username pill card --> |
||||
<Border |
||||
BoxShadow="inset 1.2 0 80 1.8 #66000000" |
||||
CornerRadius="16" |
||||
Margin="4" |
||||
Grid.Row="0" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Bottom"> |
||||
<Border.Resources> |
||||
<DropShadowEffect |
||||
x:Key="TextDropShadowEffect" |
||||
BlurRadius="12" |
||||
Color="#FF000000" |
||||
Opacity="0.9"/> |
||||
<DropShadowEffect |
||||
x:Key="ImageDropShadowEffect" |
||||
BlurRadius="12" |
||||
Color="#FF000000" |
||||
Opacity="0.2"/> |
||||
</Border.Resources> |
||||
<Button |
||||
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding CivitModel.Creator.ProfileUrl}" |
||||
CornerRadius="16" |
||||
Classes="transparent" |
||||
Padding="10,4"> |
||||
<StackPanel Orientation="Horizontal" Spacing="6"> |
||||
<controls:BetterAdvancedImage |
||||
Width="22" |
||||
Height="22" |
||||
Effect="{StaticResource ImageDropShadowEffect}" |
||||
CornerRadius="11" |
||||
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||
IsVisible="{Binding CivitModel.Creator.Image, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||
Source="{Binding CivitModel.Creator.Image}"/> |
||||
<TextBlock |
||||
VerticalAlignment="Center" |
||||
Effect="{StaticResource TextDropShadowEffect}" |
||||
Text="{Binding CivitModel.Creator.Username}"/> |
||||
</StackPanel> |
||||
</Button> |
||||
</Border> |
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal"> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="8,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="info"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding CivitModel.Type}" /> |
||||
</controls:Card> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="4,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="info"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding CivitModel.BaseModelType}" /> |
||||
</controls:Card> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="4,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="success" |
||||
IsVisible="{Binding ShowUpdateCard}"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding UpdateCardText}" /> |
||||
</controls:Card> |
||||
</StackPanel> |
||||
<Border |
||||
Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
Margin="0,0,0,0" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Stretch" |
||||
Background="#DD000000" |
||||
CornerRadius="8" |
||||
IsVisible="{Binding IsImporting}" |
||||
ZIndex="1" /> |
||||
<StackPanel |
||||
Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Center" |
||||
IsVisible="{Binding IsImporting}" |
||||
Orientation="Vertical" |
||||
ZIndex="2"> |
||||
<controls:ProgressRing |
||||
Width="120" |
||||
Height="120" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
EndAngle="450" |
||||
IsIndeterminate="False" |
||||
StartAngle="90" |
||||
Value="{Binding Value}" /> |
||||
<TextBlock |
||||
Margin="0,8,0,0" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding Text, TargetNullValue=Importing...}" /> |
||||
</StackPanel> |
||||
|
||||
<Border |
||||
Name="ModelCardBottom" |
||||
Grid.Row="1"> |
||||
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto, Auto"> |
||||
|
||||
<!-- |
||||
TextTrimming causing issues with unicode chars until |
||||
https://github.com/AvaloniaUI/Avalonia/pull/13385 is released |
||||
--> |
||||
<TextBlock |
||||
Grid.ColumnSpan="2" |
||||
MaxWidth="250" |
||||
Margin="8,0,8,0" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Center" |
||||
FontWeight="SemiBold" |
||||
Foreground="{DynamicResource TextControlForeground}" |
||||
LetterSpacing="0.33" |
||||
Text="{Binding CivitModel.Name}" |
||||
TextWrapping="NoWrap" |
||||
ToolTip.Tip="{Binding CivitModel.Name}" /> |
||||
|
||||
<TextBlock |
||||
Grid.Row="1" |
||||
Grid.Column="0" |
||||
Margin="8,-4,0,0" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
Foreground="{DynamicResource TextControlForeground}" |
||||
Text="{Binding CivitModel.LatestModelVersionName, FallbackValue=''}" /> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
Grid.Column="0" |
||||
Orientation="Horizontal"> |
||||
|
||||
<controls:StarsRating |
||||
Margin="8,8,0,8" |
||||
Background="#66000000" |
||||
FontSize="16" |
||||
Foreground="{DynamicResource ThemeEldenRingOrangeColor}" |
||||
Value="{Binding CivitModel.Stats.Rating}" /> |
||||
<TextBlock |
||||
Margin="4,0,0,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.RatingCount}" |
||||
TextAlignment="Center" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
Grid.Column="1" |
||||
HorizontalAlignment="Right" |
||||
Orientation="Horizontal"> |
||||
<avalonia:Icon Value="fa-solid fa-heart" /> |
||||
<TextBlock |
||||
Margin="4,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.FavoriteCount, Converter={StaticResource KiloFormatterConverter}}" /> |
||||
|
||||
<avalonia:Icon Margin="4,0" Value="fa-solid fa-download" /> |
||||
<TextBlock |
||||
Margin="0,0,4,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.DownloadCount, Converter={StaticResource KiloFormatterConverter}}" /> |
||||
</StackPanel> |
||||
<Button |
||||
Grid.Row="0" |
||||
Grid.Column="1" |
||||
Width="32" |
||||
Margin="0,4,4,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
HorizontalContentAlignment="Right" |
||||
VerticalContentAlignment="Top" |
||||
BorderThickness="0" |
||||
Classes="transparent"> |
||||
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" /> |
||||
<Button.Flyout> |
||||
<MenuFlyout> |
||||
<MenuItem Command="{Binding OpenModelCommand}" Header="{x:Static lang:Resources.Action_OpenOnCivitAi}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Button.Flyout> |
||||
</Button> |
||||
</Grid> |
||||
</Border> |
||||
</Grid> |
||||
</Button> |
||||
</Border> |
||||
|
||||
</DataTemplate> |
||||
</UserControl.Resources> |
||||
|
||||
<Grid Margin="0,8,0,0" RowDefinitions="Auto,*,Auto"> |
||||
|
||||
<StackPanel Margin="8" Orientation="Vertical"> |
||||
<Grid ColumnDefinitions="*,Auto"> |
||||
|
||||
<TextBox |
||||
Margin="8,0,0,0" |
||||
HorizontalAlignment="Stretch" |
||||
KeyDown="InputElement_OnKeyDown" |
||||
Text="{Binding SearchQuery, Mode=TwoWay}" |
||||
Watermark="{x:Static lang:Resources.Label_ModelSearchWatermark}"> |
||||
<TextBox.InnerRightContent> |
||||
<Button |
||||
Classes="transparent-full" |
||||
Command="{Binding ClearSearchQuery}" |
||||
IsVisible="{Binding SearchQuery.Length}"> |
||||
<ui:SymbolIcon Symbol="Cancel" /> |
||||
</Button> |
||||
</TextBox.InnerRightContent> |
||||
</TextBox> |
||||
|
||||
<Button |
||||
Grid.Column="1" |
||||
Width="80" |
||||
Margin="8,0,8,0" |
||||
VerticalAlignment="Stretch" |
||||
Classes="accent" |
||||
Command="{Binding SearchModelsCommand}" |
||||
IsDefault="True"> |
||||
<Grid> |
||||
<controls:ProgressRing |
||||
MinWidth="16" |
||||
MinHeight="16" |
||||
VerticalAlignment="Center" |
||||
BorderThickness="4" |
||||
IsIndeterminate="True" |
||||
IsVisible="{Binding SearchModelsCommand.IsRunning}" /> |
||||
<TextBlock |
||||
VerticalAlignment="Center" |
||||
IsVisible="{Binding !SearchModelsCommand.IsRunning}" |
||||
Text="{x:Static lang:Resources.Action_Search}" /> |
||||
</Grid> |
||||
</Button> |
||||
</Grid> |
||||
<DockPanel> |
||||
<StackPanel Margin="8,8,4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_Sort}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllSortModes}" |
||||
SelectedItem="{Binding SortMode}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_TimePeriod}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllCivitPeriods}" |
||||
SelectedItem="{Binding SelectedPeriod}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_ModelType}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllModelTypes}" |
||||
SelectedItem="{Binding SelectedModelType}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_BaseModel}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding BaseModelOptions}" |
||||
SelectedItem="{Binding SelectedBaseModelType}" /> |
||||
</StackPanel> |
||||
|
||||
|
||||
<CheckBox |
||||
Margin="8,8,8,0" |
||||
HorizontalAlignment="Right" |
||||
Content="{x:Static lang:Resources.Label_ShowNsfwContent}" |
||||
IsChecked="{Binding ShowNsfw, Mode=TwoWay}" /> |
||||
</DockPanel> |
||||
|
||||
</StackPanel> |
||||
|
||||
<ScrollViewer |
||||
Grid.Row="1" |
||||
Margin="8,0,8,0" |
||||
ScrollChanged="ScrollViewer_OnScrollChanged"> |
||||
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}" |
||||
HorizontalAlignment="Center" |
||||
ItemsSource="{Binding ModelCards}"> |
||||
<ItemsRepeater.Layout> |
||||
<UniformGridLayout Orientation="Horizontal" /> |
||||
</ItemsRepeater.Layout> |
||||
</ItemsRepeater> |
||||
</ScrollViewer> |
||||
|
||||
<TextBlock |
||||
Grid.Row="2" |
||||
Margin="16,8" |
||||
VerticalAlignment="Bottom" |
||||
Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" /> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
Margin="8" |
||||
HorizontalAlignment="Center" |
||||
IsVisible="{Binding HasSearched}" |
||||
Orientation="Vertical"> |
||||
<TextBlock Margin="0,0,4,4" TextAlignment="Center"> |
||||
<Run Text="{x:Static lang:Resources.Label_Page}" /> |
||||
<Run Text="{Binding CurrentPageNumber, FallbackValue=1}" /> |
||||
<Run Text="/" /> |
||||
<Run Text="{Binding TotalPages, FallbackValue=5}" /> |
||||
</TextBlock> |
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> |
||||
<Button |
||||
Margin="0,0,8,0" |
||||
Command="{Binding FirstPage}" |
||||
IsEnabled="{Binding CanGoToFirstPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_FirstPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-backward-fast" /> |
||||
</Button> |
||||
<Button |
||||
Margin="0,0,8,0" |
||||
Command="{Binding PreviousPage}" |
||||
IsEnabled="{Binding CanGoToPreviousPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_PreviousPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-caret-left" /> |
||||
</Button> |
||||
<Button |
||||
Margin="0,0,8,0" |
||||
Command="{Binding NextPage}" |
||||
IsEnabled="{Binding CanGoToNextPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_NextPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-caret-right" /> |
||||
</Button> |
||||
<Button |
||||
Command="{Binding LastPage}" |
||||
IsEnabled="{Binding CanGoToLastPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_LastPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-forward-fast" /> |
||||
</Button> |
||||
</StackPanel> |
||||
</StackPanel> |
||||
|
||||
<TextBlock |
||||
Grid.Row="0" |
||||
Grid.RowSpan="3" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="20" |
||||
IsVisible="{Binding NoResultsFound}" |
||||
Text="{Binding NoResultsText, FallbackValue=No results found}" /> |
||||
|
||||
<controls:ProgressRing |
||||
Grid.Row="1" |
||||
Width="128" |
||||
Height="128" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Stretch" |
||||
IsIndeterminate="True" |
||||
IsVisible="{Binding ShowMainLoadingSpinner, FallbackValue=False}" /> |
||||
</Grid> |
||||
</UserControl> |
||||
mc:Ignorable="d" |
||||
x:Class="StabilityMatrix.Avalonia.Views.CheckpointBrowserPage"> |
||||
<TabControl ItemsSource="{Binding Pages}"/> |
||||
</controls:UserControlBase> |
||||
|
@ -0,0 +1,531 @@
|
||||
<controls:UserControlBase |
||||
x:Class="StabilityMatrix.Avalonia.Views.CivitAiBrowserPage" |
||||
xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" |
||||
xmlns:checkpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" |
||||
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager" |
||||
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" |
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia" |
||||
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" |
||||
d:DataContext="{x:Static designData:DesignData.CivitAiBrowserViewModel}" |
||||
d:DesignHeight="700" |
||||
d:DesignWidth="800" |
||||
x:CompileBindings="True" |
||||
x:DataType="checkpointBrowser:CivitAiBrowserViewModel" |
||||
mc:Ignorable="d"> |
||||
|
||||
<UserControl.Styles> |
||||
<Style Selector="Border#HoverBorder"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237"/> |
||||
</Transitions> |
||||
</Setter> |
||||
|
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="Transitions"> |
||||
<Transitions> |
||||
<TransformOperationsTransition Property="RenderTransform" |
||||
Duration="0:0:0.237"> |
||||
<TransformOperationsTransition.Easing> |
||||
<QuadraticEaseInOut/> |
||||
</TransformOperationsTransition.Easing> |
||||
</TransformOperationsTransition> |
||||
</Transitions> |
||||
</Setter> |
||||
</Style> |
||||
|
||||
<Style Selector="^:pointerover"> |
||||
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000"/> |
||||
<Setter Property="Cursor" Value="Hand" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="12"/> |
||||
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)"/> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#CC000000" /> |
||||
</Style> |
||||
</Style> |
||||
|
||||
<Style Selector="^:not(:pointerover)"> |
||||
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000"/> |
||||
<Setter Property="Cursor" Value="Arrow" /> |
||||
<Style Selector="^ asyncImageLoader|AdvancedImage"> |
||||
<Setter Property="CornerRadius" Value="8"/> |
||||
<Setter Property="RenderTransform" Value="scale(1, 1)"/> |
||||
</Style> |
||||
<Style Selector="^ Border#ModelCardBottom"> |
||||
<Setter Property="Background" Value="#99000000" /> |
||||
</Style> |
||||
</Style> |
||||
</Style> |
||||
</UserControl.Styles> |
||||
|
||||
<UserControl.Resources> |
||||
<converters:KiloFormatterStringConverter x:Key="KiloFormatterConverter"/> |
||||
<DataTemplate x:Key="CivitModelTemplate" DataType="{x:Type checkpointBrowser:CheckpointBrowserCardViewModel}"> |
||||
<Border |
||||
Name="HoverBorder" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
Margin="8" |
||||
ClipToBounds="True" |
||||
CornerRadius="8"> |
||||
<Border.ContextFlyout> |
||||
<MenuFlyout> |
||||
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnCivitAi}" |
||||
Command="{Binding OpenModelCommand}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Border.ContextFlyout> |
||||
<Button |
||||
Name="ModelCard" |
||||
Classes="transparent-full" |
||||
Padding="0" |
||||
BorderThickness="0" |
||||
VerticalContentAlignment="Top" |
||||
CornerRadius="8" |
||||
Command="{Binding ShowVersionDialogCommand}" |
||||
CommandParameter="{Binding CivitModel}" |
||||
IsEnabled="{Binding !IsImporting}"> |
||||
<Grid RowDefinitions="*, Auto"> |
||||
<controls:BetterAdvancedImage |
||||
Grid.RowSpan="2" |
||||
CornerRadius="8" |
||||
Width="330" |
||||
Height="400" |
||||
Source="{Binding CardImage}" |
||||
Stretch="UniformToFill" |
||||
StretchDirection="Both"/> |
||||
|
||||
<StackPanel |
||||
Grid.Row="0" |
||||
HorizontalAlignment="Right" |
||||
Orientation="Horizontal"> |
||||
<Button |
||||
Margin="0,8,8,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
Classes="transparent-info" |
||||
Command="{Binding ToggleFavoriteCommand}" |
||||
FontSize="20" |
||||
IsVisible="{Binding !IsFavorite}"> |
||||
<Grid> |
||||
<ui:SymbolIcon Symbol="StarAdd" /> |
||||
</Grid> |
||||
</Button> |
||||
<Button |
||||
Margin="0,8,8,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
Classes="success" |
||||
Command="{Binding ToggleFavoriteCommand}" |
||||
FontSize="20" |
||||
IsVisible="{Binding IsFavorite}"> |
||||
<Grid> |
||||
<ui:SymbolIcon Symbol="StarFilled" /> |
||||
</Grid> |
||||
</Button> |
||||
</StackPanel> |
||||
|
||||
<!-- Username pill card --> |
||||
<Border |
||||
BoxShadow="inset 1.2 0 80 1.8 #66000000" |
||||
CornerRadius="16" |
||||
Margin="4" |
||||
Grid.Row="0" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Bottom"> |
||||
<Border.Resources> |
||||
<DropShadowEffect |
||||
x:Key="TextDropShadowEffect" |
||||
BlurRadius="12" |
||||
Color="#FF000000" |
||||
Opacity="0.9"/> |
||||
<DropShadowEffect |
||||
x:Key="ImageDropShadowEffect" |
||||
BlurRadius="12" |
||||
Color="#FF000000" |
||||
Opacity="0.2"/> |
||||
</Border.Resources> |
||||
<Button |
||||
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding CivitModel.Creator.ProfileUrl}" |
||||
CornerRadius="16" |
||||
Classes="transparent" |
||||
Padding="10,4"> |
||||
<StackPanel Orientation="Horizontal" Spacing="6"> |
||||
<controls:BetterAdvancedImage |
||||
Width="22" |
||||
Height="22" |
||||
Effect="{StaticResource ImageDropShadowEffect}" |
||||
CornerRadius="11" |
||||
RenderOptions.BitmapInterpolationMode="HighQuality" |
||||
IsVisible="{Binding CivitModel.Creator.Image, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" |
||||
Source="{Binding CivitModel.Creator.Image}"/> |
||||
<TextBlock |
||||
VerticalAlignment="Center" |
||||
Effect="{StaticResource TextDropShadowEffect}" |
||||
Text="{Binding CivitModel.Creator.Username}"/> |
||||
</StackPanel> |
||||
</Button> |
||||
</Border> |
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal"> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="8,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="info"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding CivitModel.Type}" /> |
||||
</controls:Card> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="4,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="info"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding CivitModel.BaseModelType}" /> |
||||
</controls:Card> |
||||
<controls:Card |
||||
Height="24" |
||||
Margin="4,8,0,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Top" |
||||
Classes="success" |
||||
IsVisible="{Binding ShowUpdateCard}"> |
||||
|
||||
<TextBlock |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
FontWeight="Medium" |
||||
Text="{Binding UpdateCardText}" /> |
||||
</controls:Card> |
||||
</StackPanel> |
||||
<Border |
||||
Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
Margin="0,0,0,0" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Stretch" |
||||
Background="#DD000000" |
||||
CornerRadius="8" |
||||
IsVisible="{Binding IsImporting}" |
||||
ZIndex="1" /> |
||||
<StackPanel |
||||
Grid.Row="0" |
||||
Grid.RowSpan="2" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Center" |
||||
IsVisible="{Binding IsImporting}" |
||||
Orientation="Vertical" |
||||
ZIndex="2"> |
||||
<controls:ProgressRing |
||||
Width="120" |
||||
Height="120" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
EndAngle="450" |
||||
IsIndeterminate="False" |
||||
StartAngle="90" |
||||
Value="{Binding Value}" /> |
||||
<TextBlock |
||||
Margin="0,8,0,0" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding Text, TargetNullValue=Importing...}" /> |
||||
</StackPanel> |
||||
|
||||
<Border |
||||
Name="ModelCardBottom" |
||||
Grid.Row="1"> |
||||
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto, Auto"> |
||||
|
||||
<!-- |
||||
TextTrimming causing issues with unicode chars until |
||||
https://github.com/AvaloniaUI/Avalonia/pull/13385 is released |
||||
--> |
||||
<TextBlock |
||||
Grid.ColumnSpan="2" |
||||
MaxWidth="250" |
||||
Margin="8,0,8,0" |
||||
HorizontalAlignment="Left" |
||||
VerticalAlignment="Center" |
||||
FontWeight="SemiBold" |
||||
Foreground="{DynamicResource TextControlForeground}" |
||||
LetterSpacing="0.33" |
||||
Text="{Binding CivitModel.Name}" |
||||
TextWrapping="NoWrap" |
||||
ToolTip.Tip="{Binding CivitModel.Name}" /> |
||||
|
||||
<TextBlock |
||||
Grid.Row="1" |
||||
Grid.Column="0" |
||||
Margin="8,-4,0,0" |
||||
VerticalAlignment="Center" |
||||
FontSize="11" |
||||
Foreground="{DynamicResource TextControlForeground}" |
||||
Text="{Binding CivitModel.LatestModelVersionName, FallbackValue=''}" /> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
Grid.Column="0" |
||||
Orientation="Horizontal"> |
||||
|
||||
<controls:StarsRating |
||||
Margin="8,8,0,8" |
||||
Background="#66000000" |
||||
FontSize="16" |
||||
Foreground="{DynamicResource ThemeEldenRingOrangeColor}" |
||||
Value="{Binding CivitModel.Stats.Rating}" /> |
||||
<TextBlock |
||||
Margin="4,0,0,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.RatingCount}" |
||||
TextAlignment="Center" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel |
||||
Grid.Row="2" |
||||
Grid.Column="1" |
||||
HorizontalAlignment="Right" |
||||
Orientation="Horizontal"> |
||||
<avalonia:Icon Value="fa-solid fa-heart" /> |
||||
<TextBlock |
||||
Margin="4,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.FavoriteCount, Converter={StaticResource KiloFormatterConverter}}" /> |
||||
|
||||
<avalonia:Icon Margin="4,0" Value="fa-solid fa-download" /> |
||||
<TextBlock |
||||
Margin="0,0,4,0" |
||||
VerticalAlignment="Center" |
||||
Text="{Binding CivitModel.Stats.DownloadCount, Converter={StaticResource KiloFormatterConverter}}" /> |
||||
</StackPanel> |
||||
<Button |
||||
Grid.Row="0" |
||||
Grid.Column="1" |
||||
Width="32" |
||||
Margin="0,4,4,0" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
HorizontalContentAlignment="Right" |
||||
VerticalContentAlignment="Top" |
||||
BorderThickness="0" |
||||
Classes="transparent"> |
||||
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" /> |
||||
<Button.Flyout> |
||||
<MenuFlyout> |
||||
<MenuItem Command="{Binding OpenModelCommand}" Header="{x:Static lang:Resources.Action_OpenOnCivitAi}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Button.Flyout> |
||||
</Button> |
||||
</Grid> |
||||
</Border> |
||||
</Grid> |
||||
</Button> |
||||
</Border> |
||||
|
||||
</DataTemplate> |
||||
</UserControl.Resources> |
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto"> |
||||
|
||||
<StackPanel Margin="8" Orientation="Vertical"> |
||||
<Grid ColumnDefinitions="*,Auto"> |
||||
|
||||
<TextBox |
||||
HorizontalAlignment="Stretch" |
||||
KeyDown="InputElement_OnKeyDown" |
||||
Text="{Binding SearchQuery, Mode=TwoWay}" |
||||
Watermark="{x:Static lang:Resources.Label_ModelSearchWatermark}"> |
||||
<TextBox.InnerRightContent> |
||||
<Button |
||||
Classes="transparent-full" |
||||
Command="{Binding ClearSearchQuery}" |
||||
IsVisible="{Binding SearchQuery.Length}"> |
||||
<ui:SymbolIcon Symbol="Cancel" /> |
||||
</Button> |
||||
</TextBox.InnerRightContent> |
||||
</TextBox> |
||||
|
||||
<Button |
||||
Grid.Column="1" |
||||
Width="80" |
||||
Margin="8,0,8,0" |
||||
VerticalAlignment="Stretch" |
||||
Classes="accent" |
||||
Command="{Binding SearchModelsCommand}" |
||||
IsDefault="True"> |
||||
<Grid> |
||||
<controls:ProgressRing |
||||
MinWidth="16" |
||||
MinHeight="16" |
||||
VerticalAlignment="Center" |
||||
BorderThickness="4" |
||||
IsIndeterminate="True" |
||||
IsVisible="{Binding SearchModelsCommand.IsRunning}" /> |
||||
<TextBlock |
||||
VerticalAlignment="Center" |
||||
IsVisible="{Binding !SearchModelsCommand.IsRunning}" |
||||
Text="{x:Static lang:Resources.Action_Search}" /> |
||||
</Grid> |
||||
</Button> |
||||
</Grid> |
||||
<DockPanel> |
||||
<StackPanel Margin="0,8,4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_Sort}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllSortModes}" |
||||
SelectedItem="{Binding SortMode}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_TimePeriod}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllCivitPeriods}" |
||||
SelectedItem="{Binding SelectedPeriod}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_ModelType}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding AllModelTypes}" |
||||
SelectedItem="{Binding SelectedModelType}" /> |
||||
</StackPanel> |
||||
|
||||
<StackPanel Margin="4,8" Orientation="Vertical"> |
||||
<Label Content="{x:Static lang:Resources.Label_BaseModel}" /> |
||||
<ComboBox |
||||
MinWidth="100" |
||||
ItemsSource="{Binding BaseModelOptions}" |
||||
SelectedItem="{Binding SelectedBaseModelType}" /> |
||||
</StackPanel> |
||||
|
||||
|
||||
<CheckBox |
||||
Margin="8,8,0,0" |
||||
HorizontalAlignment="Right" |
||||
Content="{x:Static lang:Resources.Label_ShowNsfwContent}" |
||||
IsChecked="{Binding ShowNsfw, Mode=TwoWay}" /> |
||||
</DockPanel> |
||||
|
||||
</StackPanel> |
||||
|
||||
<ScrollViewer |
||||
Grid.Row="1" |
||||
ScrollChanged="ScrollViewer_OnScrollChanged"> |
||||
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}" |
||||
HorizontalAlignment="Center" |
||||
ItemsSource="{Binding ModelCards}"> |
||||
<ItemsRepeater.Layout> |
||||
<UniformGridLayout Orientation="Horizontal" /> |
||||
</ItemsRepeater.Layout> |
||||
</ItemsRepeater> |
||||
</ScrollViewer> |
||||
|
||||
<TextBlock |
||||
Grid.Row="2" |
||||
Margin="8,8" |
||||
VerticalAlignment="Center" |
||||
Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" /> |
||||
|
||||
<StackPanel Grid.Row="2" |
||||
HorizontalAlignment="Center" |
||||
IsVisible="{Binding HasSearched}" |
||||
Margin="0,8,0,8" |
||||
Orientation="Horizontal"> |
||||
<Button |
||||
Margin="0,0,8,0" |
||||
Command="{Binding FirstPage}" |
||||
IsEnabled="{Binding CanGoToFirstPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_FirstPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-backward-fast" /> |
||||
</Button> |
||||
<Button |
||||
Margin="0,0,16,0" |
||||
Command="{Binding PreviousPage}" |
||||
IsEnabled="{Binding CanGoToPreviousPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_PreviousPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-caret-left" /> |
||||
</Button> |
||||
<TextBlock Margin="8,0,4,0" TextAlignment="Center" |
||||
Text="{x:Static lang:Resources.Label_Page}" |
||||
VerticalAlignment="Center"/> |
||||
<ui:NumberBox Value="{Binding CurrentPageNumber, FallbackValue=1}" |
||||
VerticalAlignment="Center" |
||||
SpinButtonPlacementMode="Hidden" |
||||
TextAlignment="Center"/> |
||||
<TextBlock Margin="4,0,8,0" VerticalAlignment="Center"> |
||||
<Run Text="/"/> |
||||
<Run Text="{Binding TotalPages, FallbackValue=5}"/> |
||||
</TextBlock> |
||||
<Button |
||||
Margin="16,0,8,0" |
||||
Command="{Binding NextPage}" |
||||
IsEnabled="{Binding CanGoToNextPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_NextPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-caret-right" /> |
||||
</Button> |
||||
<Button |
||||
Command="{Binding LastPage}" |
||||
IsEnabled="{Binding CanGoToLastPage}" |
||||
ToolTip.Tip="{x:Static lang:Resources.Label_LastPage}"> |
||||
<avalonia:Icon Value="fa-solid fa-forward-fast" /> |
||||
</Button> |
||||
</StackPanel> |
||||
|
||||
<TextBlock |
||||
Grid.Row="0" |
||||
Grid.RowSpan="3" |
||||
HorizontalAlignment="Center" |
||||
VerticalAlignment="Center" |
||||
FontSize="20" |
||||
IsVisible="{Binding NoResultsFound}" |
||||
Text="{Binding NoResultsText, FallbackValue=No results found}" /> |
||||
|
||||
<controls:ProgressRing |
||||
Grid.Row="1" |
||||
Width="128" |
||||
Height="128" |
||||
HorizontalAlignment="Stretch" |
||||
VerticalAlignment="Stretch" |
||||
IsIndeterminate="True" |
||||
IsVisible="{Binding ShowMainLoadingSpinner, FallbackValue=False}" /> |
||||
</Grid> |
||||
</controls:UserControlBase> |
@ -0,0 +1,41 @@
|
||||
using System.Diagnostics; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Input; |
||||
using Avalonia.Markup.Xaml; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Avalonia.ViewModels; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using CivitAiBrowserViewModel = StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser.CivitAiBrowserViewModel; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views; |
||||
|
||||
[Singleton] |
||||
public partial class CivitAiBrowserPage : UserControlBase |
||||
{ |
||||
public CivitAiBrowserPage() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
private void InitializeComponent() |
||||
{ |
||||
AvaloniaXamlLoader.Load(this); |
||||
} |
||||
|
||||
private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) |
||||
{ |
||||
if (sender is not ScrollViewer scrollViewer) |
||||
return; |
||||
|
||||
var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum; |
||||
Debug.WriteLine($"IsAtEnd: {isAtEnd}"); |
||||
} |
||||
|
||||
private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) |
||||
{ |
||||
if (e.Key == Key.Escape && DataContext is CivitAiBrowserViewModel viewModel) |
||||
{ |
||||
viewModel.ClearSearchQuery(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,188 @@
|
||||
<controls:UserControlBase |
||||
x:Class="StabilityMatrix.Avalonia.Views.HuggingFacePage" |
||||
xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:ui="using:FluentAvalonia.UI.Controls" |
||||
xmlns:huggingFacePage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.HuggingFacePage" |
||||
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers" |
||||
xmlns:checkpointBrowser="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser" |
||||
d:DataContext="{x:Static mocks:DesignData.HuggingFacePageViewModel}" |
||||
d:DesignHeight="650" |
||||
d:DesignWidth="800" |
||||
x:CompileBindings="True" |
||||
x:DataType="checkpointBrowser:HuggingFacePageViewModel" |
||||
Focusable="True" |
||||
mc:Ignorable="d"> |
||||
<ScrollViewer> |
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto" |
||||
ColumnDefinitions="Auto, *" |
||||
Margin="8" |
||||
HorizontalAlignment="Stretch"> |
||||
<TextBlock Grid.Column="1" |
||||
VerticalAlignment="Center" |
||||
IsVisible="{Binding !!NumSelected}" |
||||
Margin="8,0,8,0"> |
||||
<Run Text="{Binding NumSelected}" /> |
||||
<Run Text="models selected" /> |
||||
</TextBlock> |
||||
|
||||
<ui:CommandBar |
||||
Grid.Row="0" |
||||
Grid.Column="0" |
||||
Margin="0,-1,0,0" |
||||
VerticalAlignment="Center" |
||||
HorizontalAlignment="Left" |
||||
VerticalContentAlignment="Center" |
||||
DefaultLabelPosition="Right"> |
||||
<ui:CommandBar.PrimaryCommands> |
||||
<ui:CommandBarButton |
||||
IsEnabled="{Binding !!NumSelected}" |
||||
IconSource="Download" |
||||
Foreground="{DynamicResource AccentButtonBackground}" |
||||
Command="{Binding ImportSelectedCommand}" |
||||
Label="{x:Static lang:Resources.Action_Import}" /> |
||||
<ui:CommandBarSeparator /> |
||||
<ui:CommandBarButton |
||||
Command="{Binding SelectAll}" |
||||
IconSource="SelectAll" |
||||
Label="{x:Static lang:Resources.Action_SelectAll}" /> |
||||
<ui:CommandBarButton |
||||
IconSource="ClearSelection" |
||||
Command="{Binding ClearSelection}" |
||||
IsEnabled="{Binding !!NumSelected}" |
||||
Label="{x:Static lang:Resources.Action_ClearSelection}" /> |
||||
<ui:CommandBarSeparator IsVisible="{Binding !!NumSelected}" /> |
||||
</ui:CommandBar.PrimaryCommands> |
||||
</ui:CommandBar> |
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" |
||||
Orientation="Vertical" |
||||
Margin="0,8" |
||||
VerticalAlignment="Bottom"> |
||||
<TextBlock Text="{Binding DownloadPercentText}" |
||||
TextAlignment="Center" |
||||
IsVisible="{Binding !!TotalProgress.Total}" |
||||
Margin="0,4" |
||||
HorizontalAlignment="Stretch" /> |
||||
<ProgressBar Value="{Binding TotalProgress.Current}" |
||||
Maximum="{Binding TotalProgress.Total}" |
||||
IsVisible="{Binding !!TotalProgress.Total}" |
||||
IsIndeterminate="False" /> |
||||
</StackPanel> |
||||
|
||||
<ItemsRepeater Grid.Row="2" |
||||
Grid.Column="0" |
||||
Grid.ColumnSpan="2" |
||||
ItemsSource="{Binding Categories}"> |
||||
<ItemsRepeater.Layout> |
||||
<StackLayout Orientation="Vertical" /> |
||||
</ItemsRepeater.Layout> |
||||
<ItemsRepeater.ItemTemplate> |
||||
<DataTemplate DataType="{x:Type huggingFacePage:CategoryViewModel}"> |
||||
<Expander IsExpanded="True" Margin="0,0,0,8"> |
||||
<Expander.Header> |
||||
<StackPanel Orientation="Horizontal"> |
||||
<CheckBox IsChecked="{Binding IsChecked}" |
||||
Content="{Binding Title}" /> |
||||
</StackPanel> |
||||
</Expander.Header> |
||||
<ItemsRepeater ItemsSource="{Binding Items}" |
||||
Margin="8,0"> |
||||
<ItemsRepeater.Layout> |
||||
<UniformGridLayout MinColumnSpacing="8" |
||||
MinRowSpacing="8" |
||||
MinItemWidth="220" |
||||
ItemsStretch="Uniform" /> |
||||
</ItemsRepeater.Layout> |
||||
<ItemsRepeater.ItemTemplate> |
||||
<DataTemplate DataType="{x:Type huggingFacePage:HuggingfaceItemViewModel}"> |
||||
<Button |
||||
HorizontalAlignment="Stretch" |
||||
HorizontalContentAlignment="Stretch" |
||||
Command="{Binding ToggleSelectedCommand}" |
||||
CornerRadius="8"> |
||||
<Button.ContextFlyout> |
||||
<MenuFlyout> |
||||
<MenuItem Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding RepoUrl}" |
||||
Header="{x:Static lang:Resources.Action_OpenOnHuggingFace}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
<MenuItem Header="{x:Static lang:Resources.Label_License}" |
||||
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding LicenseUrl}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Document" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Button.ContextFlyout> |
||||
<Grid ColumnDefinitions="Auto, *"> |
||||
<CheckBox |
||||
VerticalAlignment="Center" |
||||
IsChecked="{Binding IsSelected}" |
||||
Content="{Binding Item.ModelName}" /> |
||||
<Button |
||||
Grid.Column="1" |
||||
Width="24" |
||||
Margin="0,4,4,0" |
||||
Padding="4" |
||||
HorizontalAlignment="Right" |
||||
VerticalAlignment="Top" |
||||
HorizontalContentAlignment="Right" |
||||
VerticalContentAlignment="Top" |
||||
BorderThickness="0" |
||||
Classes="transparent"> |
||||
<ui:SymbolIcon FontSize="18" Symbol="MoreVertical" /> |
||||
<Button.Flyout> |
||||
<MenuFlyout> |
||||
<MenuItem Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding RepoUrl}" |
||||
Header="{x:Static lang:Resources.Action_OpenOnHuggingFace}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="Open" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
<MenuItem Header="{x:Static lang:Resources.Label_License}" |
||||
Command="{x:Static helpers:IOCommands.OpenUrlCommand}" |
||||
CommandParameter="{Binding LicenseUrl}"> |
||||
<MenuItem.Icon> |
||||
<ui:SymbolIcon Symbol="List" /> |
||||
</MenuItem.Icon> |
||||
</MenuItem> |
||||
</MenuFlyout> |
||||
</Button.Flyout> |
||||
</Button> |
||||
</Grid> |
||||
</Button> |
||||
</DataTemplate> |
||||
</ItemsRepeater.ItemTemplate> |
||||
</ItemsRepeater> |
||||
</Expander> |
||||
</DataTemplate> |
||||
</ItemsRepeater.ItemTemplate> |
||||
</ItemsRepeater> |
||||
<!-- <Expander Grid.Column="0" --> |
||||
<!-- Grid.ColumnSpan="2" --> |
||||
<!-- Grid.Row="3" --> |
||||
<!-- Header="Manual Model Download"> --> |
||||
<!-- <Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*, Auto"> --> |
||||
<!-- <TextBox Grid.Row="1" Grid.Column="0" --> |
||||
<!-- Watermark="Enter HuggingFace repo URL" --> |
||||
<!-- Margin="0,8" /> --> |
||||
<!-- <Button Grid.Row="1" Grid.Column="1" --> |
||||
<!-- Margin="8,8,0,8" --> |
||||
<!-- Classes="accent" --> |
||||
<!-- Content="Import" /> --> |
||||
<!-- </Grid> --> |
||||
<!-- </Expander> --> |
||||
</Grid> |
||||
</ScrollViewer> |
||||
</controls:UserControlBase> |
@ -0,0 +1,13 @@
|
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Core.Attributes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views; |
||||
|
||||
[Singleton] |
||||
public partial class HuggingFacePage : UserControlBase |
||||
{ |
||||
public HuggingFacePage() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
} |
@ -0,0 +1,18 @@
|
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
|
||||
namespace StabilityMatrix.Core.Services; |
||||
|
||||
public interface IMetadataImportService |
||||
{ |
||||
Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null); |
||||
|
||||
Task<ConnectedModelInfo?> GetMetadataForFile( |
||||
FilePath filePath, |
||||
IProgress<ProgressReport>? progress = null, |
||||
bool forceReimport = false |
||||
); |
||||
|
||||
Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null); |
||||
} |
@ -0,0 +1,266 @@
|
||||
using System.Diagnostics; |
||||
using System.Text.Json; |
||||
using Microsoft.Extensions.Logging; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Helper; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.Api; |
||||
using StabilityMatrix.Core.Models.Database; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
|
||||
namespace StabilityMatrix.Core.Services; |
||||
|
||||
[Transient(typeof(IMetadataImportService))] |
||||
public class MetadataImportService( |
||||
ILogger<MetadataImportService> logger, |
||||
IDownloadService downloadService, |
||||
ModelFinder modelFinder |
||||
) : IMetadataImportService |
||||
{ |
||||
public async Task ScanDirectoryForMissingInfo(DirectoryPath directory, IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true)); |
||||
|
||||
var checkpointsWithoutMetadata = directory |
||||
.EnumerateFiles(searchOption: SearchOption.AllDirectories) |
||||
.Where(FileHasNoCmInfo) |
||||
.ToList(); |
||||
|
||||
var scanned = 0; |
||||
var success = 0; |
||||
|
||||
foreach (var checkpointFilePath in checkpointsWithoutMetadata) |
||||
{ |
||||
if (scanned == 0) |
||||
{ |
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: scanned, |
||||
total: checkpointsWithoutMetadata.Count, |
||||
$"Scanning directory..." |
||||
) |
||||
); |
||||
} |
||||
else |
||||
{ |
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: scanned, |
||||
total: checkpointsWithoutMetadata.Count, |
||||
$"{success} files imported successfully" |
||||
) |
||||
); |
||||
} |
||||
|
||||
var fileNameWithoutExtension = checkpointFilePath.NameWithoutExtension; |
||||
var cmInfoPath = checkpointFilePath.Directory?.JoinFile($"{fileNameWithoutExtension}.cm-info.json"); |
||||
var cmInfoExists = File.Exists(cmInfoPath); |
||||
if (cmInfoExists) |
||||
continue; |
||||
|
||||
var hashProgress = new Progress<ProgressReport>(report => |
||||
{ |
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: report.Current ?? 0, |
||||
total: report.Total ?? 0, |
||||
$"Scanning file {scanned}/{checkpointsWithoutMetadata.Count} ... {report.Percentage}%" |
||||
) |
||||
); |
||||
}); |
||||
|
||||
var blake3 = await GetBlake3Hash(cmInfoPath, checkpointFilePath, hashProgress).ConfigureAwait(false); |
||||
if (string.IsNullOrWhiteSpace(blake3)) |
||||
{ |
||||
logger.LogWarning($"Blake3 hash was null for {checkpointFilePath}"); |
||||
scanned++; |
||||
continue; |
||||
} |
||||
|
||||
var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false); |
||||
if (modelInfo == null) |
||||
{ |
||||
logger.LogWarning($"Could not find model for {blake3}"); |
||||
scanned++; |
||||
continue; |
||||
} |
||||
|
||||
var (model, modelVersion, modelFile) = modelInfo.Value; |
||||
|
||||
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); |
||||
await updatedCmInfo |
||||
.SaveJsonToDirectory(checkpointFilePath.Directory, fileNameWithoutExtension) |
||||
.ConfigureAwait(false); |
||||
|
||||
var image = modelVersion |
||||
.Images |
||||
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))); |
||||
if (image == null) |
||||
{ |
||||
scanned++; |
||||
success++; |
||||
continue; |
||||
} |
||||
|
||||
await DownloadImage(image, checkpointFilePath, progress).ConfigureAwait(false); |
||||
|
||||
scanned++; |
||||
success++; |
||||
} |
||||
|
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: scanned, |
||||
total: checkpointsWithoutMetadata.Count, |
||||
$"Metadata found for {success}/{checkpointsWithoutMetadata.Count} files" |
||||
) |
||||
); |
||||
} |
||||
|
||||
private static bool FileHasNoCmInfo(FilePath file) |
||||
{ |
||||
return LocalModelFile.SupportedCheckpointExtensions.Contains(file.Extension) |
||||
&& !File.Exists(file.Directory?.JoinFile($"{file.NameWithoutExtension}.cm-info.json")); |
||||
} |
||||
|
||||
public async Task UpdateExistingMetadata(DirectoryPath directory, IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
progress?.Report(new ProgressReport(-1f, "Scanning directory...", isIndeterminate: true)); |
||||
|
||||
var cmInfoList = new Dictionary<FilePath, ConnectedModelInfo>(); |
||||
foreach (var cmInfoPath in directory.EnumerateFiles("*.cm-info.json", SearchOption.AllDirectories)) |
||||
{ |
||||
var cmInfo = JsonSerializer.Deserialize<ConnectedModelInfo>( |
||||
await cmInfoPath.ReadAllTextAsync().ConfigureAwait(false) |
||||
); |
||||
if (cmInfo == null) |
||||
continue; |
||||
|
||||
cmInfoList.Add(cmInfoPath, cmInfo); |
||||
} |
||||
|
||||
var success = 1; |
||||
foreach (var (filePath, cmInfoValue) in cmInfoList) |
||||
{ |
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: success, |
||||
total: cmInfoList.Count, |
||||
$"Updating metadata {success}/{cmInfoList.Count}" |
||||
) |
||||
); |
||||
|
||||
var hash = cmInfoValue.Hashes.BLAKE3; |
||||
if (string.IsNullOrWhiteSpace(hash)) |
||||
continue; |
||||
|
||||
var modelInfo = await modelFinder.RemoteFindModel(hash).ConfigureAwait(false); |
||||
if (modelInfo == null) |
||||
{ |
||||
logger.LogWarning($"Could not find model for {hash}"); |
||||
continue; |
||||
} |
||||
|
||||
var (model, modelVersion, modelFile) = modelInfo.Value; |
||||
|
||||
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); |
||||
|
||||
var nameWithoutCmInfo = filePath.NameWithoutExtension.Replace(".cm-info", string.Empty); |
||||
await updatedCmInfo.SaveJsonToDirectory(filePath.Directory, nameWithoutCmInfo).ConfigureAwait(false); |
||||
|
||||
var image = modelVersion |
||||
.Images |
||||
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))); |
||||
if (image == null) |
||||
continue; |
||||
|
||||
await DownloadImage(image, filePath, progress).ConfigureAwait(false); |
||||
|
||||
success++; |
||||
} |
||||
} |
||||
|
||||
public async Task<ConnectedModelInfo?> GetMetadataForFile( |
||||
FilePath filePath, |
||||
IProgress<ProgressReport>? progress = null, |
||||
bool forceReimport = false |
||||
) |
||||
{ |
||||
progress?.Report(new ProgressReport(-1f, "Getting metadata...", isIndeterminate: true)); |
||||
|
||||
var fileNameWithoutExtension = filePath.NameWithoutExtension; |
||||
var cmInfoPath = filePath.Directory?.JoinFile($"{fileNameWithoutExtension}.cm-info.json"); |
||||
var cmInfoExists = File.Exists(cmInfoPath); |
||||
if (cmInfoExists && !forceReimport) |
||||
return null; |
||||
|
||||
var hashProgress = new Progress<ProgressReport>(report => |
||||
{ |
||||
progress?.Report( |
||||
new ProgressReport( |
||||
current: report.Current ?? 0, |
||||
total: report.Total ?? 0, |
||||
$"Getting metadata for {filePath} ... {report.Percentage}%" |
||||
) |
||||
); |
||||
}); |
||||
var blake3 = await GetBlake3Hash(cmInfoPath, filePath, hashProgress).ConfigureAwait(false); |
||||
|
||||
if (string.IsNullOrWhiteSpace(blake3)) |
||||
{ |
||||
logger.LogWarning($"Blake3 hash was null for {filePath}"); |
||||
return null; |
||||
} |
||||
|
||||
var modelInfo = await modelFinder.RemoteFindModel(blake3).ConfigureAwait(false); |
||||
if (modelInfo == null) |
||||
{ |
||||
logger.LogWarning($"Could not find model for {blake3}"); |
||||
return null; |
||||
} |
||||
|
||||
var (model, modelVersion, modelFile) = modelInfo.Value; |
||||
|
||||
var updatedCmInfo = new ConnectedModelInfo(model, modelVersion, modelFile, DateTimeOffset.UtcNow); |
||||
await updatedCmInfo.SaveJsonToDirectory(filePath.Directory, fileNameWithoutExtension).ConfigureAwait(false); |
||||
|
||||
var image = modelVersion |
||||
.Images |
||||
?.FirstOrDefault(img => LocalModelFile.SupportedImageExtensions.Contains(Path.GetExtension(img.Url))); |
||||
|
||||
if (image == null) |
||||
return updatedCmInfo; |
||||
|
||||
await DownloadImage(image, filePath, progress).ConfigureAwait(false); |
||||
|
||||
return updatedCmInfo; |
||||
} |
||||
|
||||
private static async Task<string?> GetBlake3Hash( |
||||
FilePath? cmInfoPath, |
||||
FilePath checkpointFilePath, |
||||
IProgress<ProgressReport> hashProgress |
||||
) |
||||
{ |
||||
if (string.IsNullOrWhiteSpace(cmInfoPath?.ToString()) || !File.Exists(cmInfoPath)) |
||||
{ |
||||
return await FileHash.GetBlake3Async(checkpointFilePath, hashProgress).ConfigureAwait(false); |
||||
} |
||||
|
||||
var cmInfo = JsonSerializer.Deserialize<ConnectedModelInfo>( |
||||
await cmInfoPath.ReadAllTextAsync().ConfigureAwait(false) |
||||
); |
||||
return cmInfo?.Hashes.BLAKE3; |
||||
} |
||||
|
||||
private Task DownloadImage(CivitImage image, FilePath modelFilePath, IProgress<ProgressReport>? progress) |
||||
{ |
||||
var imageExt = Path.GetExtension(image.Url).TrimStart('.'); |
||||
var nameWithoutCmInfo = modelFilePath.NameWithoutExtension.Replace(".cm-info", string.Empty); |
||||
var imageDownloadPath = Path.GetFullPath( |
||||
Path.Combine(modelFilePath.Directory, $"{nameWithoutCmInfo}.preview.{imageExt}") |
||||
); |
||||
return downloadService.DownloadToFileAsync(image.Url, imageDownloadPath, progress); |
||||
} |
||||
} |
Loading…
Reference in new issue