Skip to content
Merged

v2.15.8 #1642

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).

## [Unreleased - v2.15.8]
## v2.15.8
### Added
- Added support for the civitai.red (mature-content) domain — NSFW CivitAI links now open and copy as civitai.red URLs, and pasting a civitai.red URL into the CivitAI model browser search works the same as a civitai.com URL
### Changed
- The CivitAI base model type filter now uses CivitAI's official `/api/v1/enums` endpoint, with fallbacks to the previous technique and a built-in list, so the filter stays populated even if the CivitAI response format changes or the service is unreachable
### Fixed
- Fixed [#1608](https://github.com/LykosAI/StabilityMatrix/issues/1608) - Crash when cdn fetch fails due to error notification not being shown on UI Thread - thanks to @NeuralFault!
- Fixed CivitAI model browsing breaking during Discovery API outages — the browser now falls back to the direct CivitAI API when Discovery returns a server error, authentication failure, or times out
- Fixed SwarmUI user settings (theme, output format, server configuration, etc.) and any user-added backend entries being overwritten when the install flow ran over an existing install — `Settings.fds` and `Backends.fds` are now merged with their existing contents instead of being rewritten from a stale template
- Fixed pip requirements handling for environment-marker dependencies - thanks to @NeuralFault!
- Fixed [#1608](https://github.com/LykosAI/StabilityMatrix/issues/1608) - Crash when cdn fetch fails due to error notification not being shown on UI Thread - thanks to @NeuralFault!
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This entry for issue #1608 is a duplicate of the one on line 14. Please remove it.

- Fixed ComfyUI-Zluda inheriting `--enable-manager` from the base ComfyUI launch options, which blocked the bundled custom-node manager from initializing - thanks to @NeuralFault!
### Supporters
#### 🌟 Visionaries
Heaps of gratitude to our Visionaries — **Waterclouds**, **bluepopsicle**, **Ibixat**, **Droolguy**, **snotty**, **LG**, and **whudunit** — for sticking with us release after release. Your encouragement, your patience while we chase down those last bugs, and the sheer fact of you being here keeps us showing up at the keyboard. We're so glad you're part of this little corner of the internet with us. And big warm welcomes again to our newest Visionaries **MrMxyzptlk12836**, **Psilocyfer18731**, **KalAbaddon**, **RustCupcake**, and **moon_milky2843** — make yourselves at home, you're among friends! 💛
#### 🚀 Pioneers
And the Pioneer crew — what a lineup. A massive thank-you to **Szir777**, **[USA]TechDude**, **takyamtom**, **SinthCore**, **Commissar Lord Death**, **Ahmed S**, **SeraphOfSalem**, and **Jisuren** — your steady presence, kind words, and patience as we've shifted things around mean more than you know. A heartfelt welcome back to **Tigon**, who's returned to the Pioneer ranks after a little time away — so glad you're back. 🎉 And a special hello to **jweg79**, who's been quietly supporting us for a while and just decided to step up and join the Pioneer crew this round — so happy to have you here. To our newest Pioneers, an enormous welcome: **rwx14662**, **Hurbie53**, **ahnhj.al**, **drew.lukas**, **Firelight**, **joeto332987**, **Tuskaruho**, **Cjloha**, **Alligator1907**, **Bitti**, **damianpointdexter**, and **tmdcks**! We're absolutely thrilled to have you with us. (And to our anonymous Pioneer out there too, our thanks reaches you — we see you. 💛)

## v2.15.7
### Added
Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<PackageVersion Include="DynamicData" Version="9.3.1" />
<PackageVersion Include="ExifLibNet" Version="2.1.4" />
<PackageVersion Include="Exceptionless.DateTimeExtensions" Version="3.4.3" />
<PackageVersion Include="FreneticLLC.FreneticUtilities" Version="1.0.32" />
<PackageVersion Include="FreneticLLC.FreneticUtilities" Version="1.1.4" />
<PackageVersion Include="FuzzySharp" Version="2.0.2" />
<PackageVersion Include="Hardware.Info" Version="100.1.0.1" />
<PackageVersion Include="Injectio" Version="4.0.0" />
Expand Down Expand Up @@ -133,4 +133,4 @@
<PackageVersion Include="xunit" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
</Project>
168 changes: 146 additions & 22 deletions StabilityMatrix.Avalonia/Services/CivitBaseModelTypeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,65 @@ ILiteDbContext dbContext
{
private const string CacheId = "BaseModelTypes";
private static readonly TimeSpan CacheExpiration = TimeSpan.FromHours(24);
private const string LegacyBaseModelListProbe = "gimmethelist";
private static readonly IReadOnlyList<string> KnownVisibleBaseModelTypes =
[
"Anima",
"AuraFlow",
"Chroma",
"CogVideoX",
"Flux.1 D",
"Flux.1 Kontext",
"Flux.1 Krea",
"Flux.1 S",
"Flux.2 D",
"Flux.2 Klein 4B",
"Flux.2 Klein 4B-base",
"Flux.2 Klein 9B",
"Flux.2 Klein 9B-base",
"Grok",
"HiDream",
"Hunyuan 1",
"Hunyuan Video",
"Illustrious",
"Kolors",
"LTXV",
"LTXV 2.3",
"LTXV2",
"Lumina",
"Mochi",
"NoobAI",
"Other",
"PixArt E",
"PixArt a",
"Pony",
"Pony V7",
"Qwen",
"Qwen 2",
"SD 1.4",
"SD 1.5",
"SD 1.5 Hyper",
"SD 1.5 LCM",
"SD 2.0",
"SD 2.1",
"SDXL 1.0",
"SDXL Hyper",
"SDXL Lightning",
"Upscaler",
"Wan Image 2.7",
"Wan Video 1.3B t2v",
"Wan Video 14B i2v 480p",
"Wan Video 14B i2v 720p",
"Wan Video 14B t2v",
"Wan Video 2.2 I2V-A14B",
"Wan Video 2.2 T2V-A14B",
"Wan Video 2.2 TI2V-5B",
"Wan Video 2.5 I2V",
"Wan Video 2.5 T2V",
"Wan Video 2.7",
"ZImageBase",
"ZImageTurbo",
];

/// <summary>
/// Gets the list of base model types, using cache if available and not expired
Expand All @@ -39,15 +98,11 @@ public async Task<List<string>> GetBaseModelTypes(bool forceRefresh = false, boo
{
if (civitBaseModels.Count <= 0)
{
var baseModelsResponse = await civitApi.GetBaseModelList();
var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync();
var baseModels = JsonNode.Parse(jsonContent);
civitBaseModels =
await TryGetBaseModelsFromEnumsEndpoint() ?? await TryGetLegacyBaseModelList() ?? [];

var innerJson = baseModels?["error"]?["message"]?.GetValue<string>();
var jArray = JsonNode.Parse(innerJson).AsArray();
var baseModelValues = jArray[0]?["errors"]?[0]?[0]?["values"]?.AsArray();

civitBaseModels = baseModelValues?.GetValues<string>().ToList() ?? [];
civitBaseModels =
civitBaseModels.Count > 0 ? civitBaseModels : GetKnownVisibleBaseModelTypes();

// Cache the results
var cacheEntry = new CivitBaseModelTypeCacheEntry
Expand All @@ -60,27 +115,18 @@ public async Task<List<string>> GetBaseModelTypes(bool forceRefresh = false, boo
await dbContext.UpsertCivitBaseModelTypeCacheEntry(cacheEntry);
}

if (includeAllOption)
{
civitBaseModels.Insert(0, CivitBaseModelType.All.ToString());
}

// Filter and sort results
var filteredResults = civitBaseModels
.Where(s => !s.Equals("odor", StringComparison.OrdinalIgnoreCase))
.OrderBy(s => s)
.ToList();

return filteredResults;
return NormalizeBaseModelTypes(civitBaseModels, includeAllOption);
}
catch (Exception e)
{
logger.LogError(e, "Failed to get base model list");

// Return cached results if available, even if expired
var expiredCache = await dbContext.GetCivitBaseModelTypeCacheEntry(CacheId);
return expiredCache?.ModelTypes
?? Enum.GetValues<CivitBaseModelType>().Select(b => b.GetStringValue()).ToList();
return NormalizeBaseModelTypes(
expiredCache?.ModelTypes ?? GetKnownVisibleBaseModelTypes(),
includeAllOption
);
}
}

Expand All @@ -91,4 +137,82 @@ public void ClearCache()
{
dbContext.CivitBaseModelTypeCache.DeleteAllAsync();
}

private async Task<List<string>?> TryGetBaseModelsFromEnumsEndpoint()
{
try
{
var enumsResponse = await civitApi.GetEnums();
var baseModels = enumsResponse?.ActiveBaseModel ?? enumsResponse?.BaseModel;

if (baseModels is { Count: > 0 })
{
return baseModels;
}

logger.LogInformation(
"CivitAI enums endpoint returned no base models; falling back to legacy/base list"
);
return null;
}
catch (Exception ex)
{
logger.LogInformation(ex, "CivitAI enums endpoint failed; falling back to legacy/base list");
return null;
}
}

private async Task<List<string>?> TryGetLegacyBaseModelList()
{
var baseModelsResponse = await civitApi.GetBaseModelList();
var jsonContent = await baseModelsResponse.Content.ReadAsStringAsync();
return TryParseLegacyBaseModelList(jsonContent);
}

private List<string>? TryParseLegacyBaseModelList(string jsonContent)
{
var baseModels = JsonNode.Parse(jsonContent);
var innerJson = baseModels?["error"]?["message"]?.GetValue<string>();
if (string.IsNullOrWhiteSpace(innerJson))
{
logger.LogInformation(
"CivitAI base model probe value '{Probe}' no longer returns the legacy validation payload; using built-in base model list",
LegacyBaseModelListProbe
);
return null;
}

var jArray = JsonNode.Parse(innerJson)?.AsArray();
var baseModelValues = jArray?[0]?["errors"]?[0]?[0]?["values"]?.AsArray();
return baseModelValues?.GetValues<string>().ToList();
}

private static List<string> GetKnownVisibleBaseModelTypes()
{
return KnownVisibleBaseModelTypes.ToList();
}

private static List<string> NormalizeBaseModelTypes(
IEnumerable<string>? baseModels,
bool includeAllOption
)
{
var normalized = (baseModels ?? [])
.Where(s => !string.IsNullOrWhiteSpace(s))
.Where(s => !s.Equals("odor", StringComparison.OrdinalIgnoreCase))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(s => s)
.ToList();

normalized.RemoveAll(s =>
s.Equals(CivitBaseModelType.All.ToString(), StringComparison.OrdinalIgnoreCase)
);

if (includeAllOption)
{
normalized.Insert(0, CivitBaseModelType.All.ToString());
}

return normalized;
}
Comment on lines +195 to +217
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

CivitBaseModelType.All.ToString() is called multiple times, including inside a RemoveAll predicate. It is more efficient to evaluate this once and store it in a local variable.

    private static List<string> NormalizeBaseModelTypes(
        IEnumerable<string>? baseModels,
        bool includeAllOption
    )
    {
        var allOptionString = CivitBaseModelType.All.ToString();
        var normalized = (baseModels ?? [])
            .Where(s => !string.IsNullOrWhiteSpace(s))
            .Where(s => !s.Equals("odor", StringComparison.OrdinalIgnoreCase))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(s => s)
            .ToList();

        normalized.RemoveAll(s =>
            s.Equals(allOptionString, StringComparison.OrdinalIgnoreCase)
        );

        if (includeAllOption)
        {
            normalized.Insert(0, allOptionString);
        }

        return normalized;
    }

}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private void UpdateImage()
[RelayCommand]
private void OpenModel()
{
ProcessRunner.OpenUrl($"https://civitai.com/models/{CivitModel.Id}");
ProcessRunner.OpenUrl(CivitaiUrlHelper.GetModelUrl(CivitModel.Id, CivitModel.Nsfw));
}

[RelayCommand]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,22 +719,18 @@ private async Task SearchModels(bool isInfiniteScroll = false)
modelRequest.Sort = CivitSortMode.HighestRated;
}
}
else if (SearchQuery.StartsWith("https://civitai.com/models/"))
else if (CivitaiUrlHelper.TryParseModelId(SearchQuery, out var modelId))
{
/* extract model ID from URL, could be one of:
https://civitai.com/models/443821?modelVersionId=1957537
https://civitai.red/models/443821?modelVersionId=1957537
https://civitai.com/models/443821/cyberrealistic-pony
https://civitai.com/models/443821
*/
var modelId = SearchQuery
.Replace("https://civitai.com/models/", string.Empty)
.Split(['?', '/'], StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault();

modelRequest.Period = CivitPeriod.AllTime;
modelRequest.BaseModels = null;
modelRequest.Types = null;
modelRequest.CommaSeparatedModelIds = modelId;
modelRequest.CommaSeparatedModelIds = modelId.ToString();

if (modelRequest.Sort is CivitSortMode.Favorites or CivitSortMode.Installed)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ IModelImportService modelImportService
) : DisposableViewModelBase
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowInferenceDefaultsSection))]
[NotifyPropertyChangedFor(nameof(ShowInferenceDefaultsSection), nameof(CivitUrl))]
public required partial CivitModel CivitModel { get; set; }

[ObservableProperty]
Expand Down Expand Up @@ -110,7 +110,8 @@ IModelImportService modelImportService
nameof(ShortSha256),
nameof(BaseModelType),
nameof(ModelFileNameFormat),
nameof(IsEarlyAccess)
nameof(IsEarlyAccess),
nameof(CivitUrl)
)]
public partial ModelVersionViewModel? SelectedVersion { get; set; }

Expand Down Expand Up @@ -172,7 +173,8 @@ IModelImportService modelImportService

public bool IsEarlyAccess => SelectedVersion?.ModelVersion.IsEarlyAccess ?? false;

public string CivitUrl => $@"https://civitai.com/models/{CivitModel.Id}";
public string CivitUrl =>
CivitaiUrlHelper.GetModelUrl(CivitModel.Id, CivitModel.Nsfw, SelectedVersion?.ModelVersion.Id);

public int DescriptionRowSpan => string.IsNullOrWhiteSpace(ModelVersionDescription) ? 3 : 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,13 @@ private void OpenOnCivitAi()
{
if (CheckpointFile.ConnectedModelInfo?.ModelId == null)
return;
ProcessRunner.OpenUrl($"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}");
ProcessRunner.OpenUrl(
CivitaiUrlHelper.GetModelUrl(
CheckpointFile.ConnectedModelInfo.ModelId.Value,
CheckpointFile.ConnectedModelInfo.Nsfw,
CheckpointFile.ConnectedModelInfo.VersionId
)
);
}

[RelayCommand]
Expand All @@ -149,7 +155,11 @@ private Task CopyModelUrl()
Task.CompletedTask,
ConnectedModelSource.Civitai when CheckpointFile.ConnectedModelInfo.ModelId != null =>
App.Clipboard.SetTextAsync(
$"https://civitai.com/models/{CheckpointFile.ConnectedModelInfo.ModelId}"
CivitaiUrlHelper.GetModelUrl(
CheckpointFile.ConnectedModelInfo.ModelId.Value,
CheckpointFile.ConnectedModelInfo.Nsfw,
CheckpointFile.ConnectedModelInfo.VersionId
)
),

ConnectedModelSource.OpenModelDb => App.Clipboard.SetTextAsync(
Expand Down
Loading
Loading