Skip to content

Commit 572c1bd

Browse files
committed
Polish inference workflow selector UX
1 parent 88d74ce commit 572c1bd

4 files changed

Lines changed: 168 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
1212
- Added an Inference workflow profile selector with recommended defaults for default checkpoints, Flux, Flux.2, Z-Image Base/Turbo, and Anima
1313
### Changed
1414
- 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
15+
- Improved the Inference workflow selector UX with Auto detection feedback, workflow-aware model lists, and recommended-default tooltips
1516
### Fixed
1617
- 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
1718
- Fixed UNet-only model selection sometimes clearing during model-list refreshes, which could hide text encoder slots after generating, cancelling, or reconnecting to ComfyUI

StabilityMatrix.Avalonia/Controls/Inference/ModelCard.axaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
Grid.Column="1"
5757
Padding="8,6,4,6"
5858
HorizontalAlignment="Stretch"
59-
ItemsSource="{Binding ClientManager.AllModels}"
59+
ItemsSource="{Binding WorkflowFilteredModels}"
6060
SelectedItem="{Binding SelectedUnifiedModel}"
6161
Theme="{StaticResource BetterComboBoxHybridModelTheme}" />
6262

@@ -144,10 +144,16 @@
144144
VerticalAlignment="Stretch"
145145
Command="{Binding ApplyRecommendedDefaultsCommand}"
146146
IsEnabled="{Binding HasRecommendedDefaults}"
147-
ToolTip.Tip="Apply recommended sampler defaults">
147+
ToolTip.Tip="{Binding RecommendedDefaultsToolTip}">
148148
<fluent:SymbolIcon FontSize="16" Symbol="Sparkle" />
149149
</Button>
150150
</Grid>
151+
<TextBlock
152+
Margin="90,4,0,0"
153+
FontSize="11"
154+
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
155+
IsVisible="{Binding ShowWorkflowProfileStatus}"
156+
Text="{Binding WorkflowProfileStatusText}" />
151157

152158
<!-- Refiner (below Model, when enabled) -->
153159
<Grid

StabilityMatrix.Avalonia/ViewModels/Dialogs/ModelPickerDialogViewModel.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using FuzzySharp;
1313
using Injectio.Attributes;
1414
using StabilityMatrix.Avalonia.Controls;
15+
using StabilityMatrix.Avalonia.Models.Inference;
1516
using StabilityMatrix.Avalonia.Services;
1617
using StabilityMatrix.Avalonia.ViewModels.Base;
1718
using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
@@ -67,6 +68,11 @@ public partial class ModelPickerDialogViewModel : ContentDialogViewModelBase
6768
/// </summary>
6869
public ModelPickerSource Source { get; set; } = ModelPickerSource.CheckpointAndUnet;
6970

71+
/// <summary>
72+
/// Optional workflow hint from Inference model cards. Used to open near compatible models.
73+
/// </summary>
74+
public InferenceWorkflowProfile PreferredWorkflowProfile { get; set; } = InferenceWorkflowProfile.Auto;
75+
7076
[ObservableProperty]
7177
private string title = "Select Model";
7278

@@ -215,6 +221,8 @@ public override void OnLoaded()
215221
if (preShowCheckpoints)
216222
ShowCheckpointsOnly = true;
217223

224+
ApplyPreferredWorkflowProfileFilters();
225+
218226
// Populate models in background after dialog appears to reduce opening hitch.
219227
Dispatcher.UIThread.Post(RefreshAllModels, DispatcherPriority.Background);
220228

@@ -434,7 +442,7 @@ private void LoadFilterStateForCurrentSource()
434442

435443
private void SaveFilterStateForCurrentSource()
436444
{
437-
if (isApplyingSavedFilterState)
445+
if (isApplyingSavedFilterState || PreferredWorkflowProfile is not InferenceWorkflowProfile.Auto)
438446
return;
439447

440448
var selectedBaseModels =
@@ -457,6 +465,41 @@ private void SaveFilterStateForCurrentSource()
457465
});
458466
}
459467

468+
private void ApplyPreferredWorkflowProfileFilters()
469+
{
470+
if (
471+
Source is not ModelPickerSource.CheckpointAndUnet
472+
|| PreferredWorkflowProfile is InferenceWorkflowProfile.Auto or InferenceWorkflowProfile.Custom
473+
)
474+
{
475+
return;
476+
}
477+
478+
if (PreferredWorkflowProfile is InferenceWorkflowProfile.DefaultCheckpoint)
479+
{
480+
ShowCheckpointsOnly = true;
481+
return;
482+
}
483+
484+
ShowUnetsOnly = true;
485+
pendingSelectedBaseModels = GetPreferredBaseModels(PreferredWorkflowProfile)
486+
.ToHashSet(StringComparer.OrdinalIgnoreCase);
487+
}
488+
489+
private static IEnumerable<string> GetPreferredBaseModels(InferenceWorkflowProfile profile)
490+
{
491+
return profile switch
492+
{
493+
InferenceWorkflowProfile.Flux => ["Flux.1"],
494+
InferenceWorkflowProfile.Flux2 => ["Flux.2"],
495+
InferenceWorkflowProfile.ZImageBase => ["ZImageBase"],
496+
InferenceWorkflowProfile.ZImageTurbo => ["ZImageTurbo"],
497+
InferenceWorkflowProfile.Anima => ["Anima"],
498+
InferenceWorkflowProfile.HiDream => ["HiDream"],
499+
_ => [],
500+
};
501+
}
502+
460503
private void UpdateFilteredModels()
461504
{
462505
var models = allModels.AsEnumerable();

StabilityMatrix.Avalonia/ViewModels/Inference/ModelCardViewModel.cs

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.ObjectModel;
2+
using System.Collections.Specialized;
23
using System.ComponentModel.DataAnnotations;
34
using System.Text.Json.Nodes;
45
using CommunityToolkit.Mvvm.ComponentModel;
@@ -14,6 +15,7 @@
1415
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
1516
using StabilityMatrix.Avalonia.ViewModels.Inference.Modules;
1617
using StabilityMatrix.Core.Attributes;
18+
using StabilityMatrix.Core.Extensions;
1719
using StabilityMatrix.Core.Models;
1820
using StabilityMatrix.Core.Models.Api.Comfy.Nodes;
1921
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes;
@@ -32,6 +34,10 @@ TabContext tabContext
3234
{
3335
[ObservableProperty]
3436
[NotifyPropertyChangedFor(nameof(SelectedUnifiedModel))]
37+
[NotifyPropertyChangedFor(nameof(WorkflowFilteredModels))]
38+
[NotifyPropertyChangedFor(nameof(WorkflowProfileStatusText))]
39+
[NotifyPropertyChangedFor(nameof(ShowWorkflowProfileStatus))]
40+
[NotifyPropertyChangedFor(nameof(RecommendedDefaultsToolTip))]
3541
private HybridModelFile? selectedModel;
3642

3743
[ObservableProperty]
@@ -40,7 +46,11 @@ TabContext tabContext
4046
nameof(ShowPrecisionSelection),
4147
nameof(SelectedUnifiedModel),
4248
nameof(HasActiveAdvancedOptions),
43-
nameof(AdvancedOptionsHeader)
49+
nameof(AdvancedOptionsHeader),
50+
nameof(WorkflowFilteredModels),
51+
nameof(WorkflowProfileStatusText),
52+
nameof(ShowWorkflowProfileStatus),
53+
nameof(RecommendedDefaultsToolTip)
4454
)]
4555
private HybridModelFile? selectedUnetModel;
4656

@@ -118,7 +128,11 @@ public HybridModelFile? SelectedUnifiedModel
118128
nameof(ShowPrecisionSelection),
119129
nameof(ShowEncoderSection),
120130
nameof(HasActiveAdvancedOptions),
121-
nameof(AdvancedOptionsHeader)
131+
nameof(AdvancedOptionsHeader),
132+
nameof(WorkflowFilteredModels),
133+
nameof(WorkflowProfileStatusText),
134+
nameof(ShowWorkflowProfileStatus),
135+
nameof(RecommendedDefaultsToolTip)
122136
)]
123137
private ModelLoader selectedModelLoader;
124138

@@ -160,7 +174,11 @@ public HybridModelFile? SelectedUnifiedModel
160174
nameof(IsHiDreamWorkflow),
161175
nameof(ShowShift),
162176
nameof(ShowEncoderTypeSelection),
163-
nameof(HasRecommendedDefaults)
177+
nameof(HasRecommendedDefaults),
178+
nameof(WorkflowFilteredModels),
179+
nameof(WorkflowProfileStatusText),
180+
nameof(ShowWorkflowProfileStatus),
181+
nameof(RecommendedDefaultsToolTip)
164182
)]
165183
private string? selectedClipType;
166184

@@ -170,7 +188,11 @@ public HybridModelFile? SelectedUnifiedModel
170188
nameof(IsHiDreamWorkflow),
171189
nameof(ShowShift),
172190
nameof(ShowEncoderTypeSelection),
173-
nameof(HasRecommendedDefaults)
191+
nameof(HasRecommendedDefaults),
192+
nameof(WorkflowFilteredModels),
193+
nameof(WorkflowProfileStatusText),
194+
nameof(ShowWorkflowProfileStatus),
195+
nameof(RecommendedDefaultsToolTip)
174196
)]
175197
private InferenceWorkflowProfile selectedWorkflowProfile = InferenceWorkflowProfile.Auto;
176198

@@ -273,6 +295,29 @@ or InferenceWorkflowProfile.Flux2
273295
or InferenceWorkflowProfile.ZImageBase
274296
or InferenceWorkflowProfile.ZImageTurbo
275297
or InferenceWorkflowProfile.Anima;
298+
public bool ShowWorkflowProfileStatus =>
299+
SelectedWorkflowProfile is InferenceWorkflowProfile.Auto
300+
&& SelectedUnifiedModel is not null
301+
&& ResolvedWorkflowProfile is not InferenceWorkflowProfile.Custom;
302+
public string WorkflowProfileStatusText => $"Detected: {ResolvedWorkflowProfile.GetStringValue()}";
303+
public string RecommendedDefaultsToolTip =>
304+
ResolvedWorkflowProfile switch
305+
{
306+
InferenceWorkflowProfile.DefaultCheckpoint =>
307+
"Apply recommended sampler defaults: Euler Ancestral / Normal / 30 steps / CFG 5",
308+
InferenceWorkflowProfile.Flux =>
309+
"Apply recommended sampler defaults: Euler / Simple / 20 steps / CFG 3.5",
310+
InferenceWorkflowProfile.Flux2 =>
311+
"Apply recommended sampler defaults: Euler / Flux2Scheduler / 20 steps / CFG 5",
312+
InferenceWorkflowProfile.ZImageBase =>
313+
"Apply recommended sampler defaults: Res Multistep / Simple / 30 steps / CFG 4",
314+
InferenceWorkflowProfile.ZImageTurbo =>
315+
"Apply recommended sampler defaults: Res Multistep / Simple / 8 steps / CFG 1",
316+
InferenceWorkflowProfile.Anima =>
317+
"Apply recommended sampler defaults: ER SDE / Simple / 30 steps / CFG 4",
318+
_ => "No recommended sampler defaults for this workflow",
319+
};
320+
public IReadOnlyList<HybridModelFile> WorkflowFilteredModels => GetWorkflowFilteredModels();
276321

277322
public event Action<InferenceWorkflowProfile>? RecommendedDefaultsRequested;
278323

@@ -286,19 +331,27 @@ protected override void OnInitialLoaded()
286331
{
287332
SetDefaultEncoderCount();
288333
}
334+
335+
ClientManager.AllModels.CollectionChanged += AllModelsOnCollectionChanged;
289336
}
290337

291338
public override void OnUnloaded()
292339
{
293340
base.OnUnloaded();
294341
ExtraNetworksStackCardViewModel.CardAdded -= ExtraNetworksStackCardViewModelOnCardAdded;
342+
ClientManager.AllModels.CollectionChanged -= AllModelsOnCollectionChanged;
295343
}
296344

297345
private void ExtraNetworksStackCardViewModelOnCardAdded(object? sender, LoadableViewModelBase e)
298346
{
299347
OnSelectedModelChanged(SelectedModel);
300348
}
301349

350+
private void AllModelsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
351+
{
352+
OnPropertyChanged(nameof(WorkflowFilteredModels));
353+
}
354+
302355
[RelayCommand]
303356
private static async Task OnConfigClickAsync()
304357
{
@@ -327,6 +380,7 @@ private async Task OpenModelPickerAsync()
327380
using var pickerScope = vmFactory.CreateScope();
328381
var pickerVm = pickerScope.ServiceManager.Get<ModelPickerDialogViewModel>();
329382
pickerVm.Title = "Select Model";
383+
pickerVm.PreferredWorkflowProfile = SelectedWorkflowProfile;
330384

331385
if (await pickerVm.GetDialog().ShowAsync() == ContentDialogResult.Primary)
332386
{
@@ -596,10 +650,18 @@ public override void LoadStateFromJsonObject(JsonObject state)
596650

597651
private InferenceWorkflowProfile InferWorkflowProfile()
598652
{
599-
if (SelectedModelLoader is not ModelLoader.Unet)
653+
return InferWorkflowProfile(
654+
SelectedUnifiedModel,
655+
SelectedModelLoader is ModelLoader.Unet
656+
|| SelectedUnifiedModel?.Local?.SharedFolderType is SharedFolderType.DiffusionModels
657+
);
658+
}
659+
660+
private static InferenceWorkflowProfile InferWorkflowProfile(HybridModelFile? model, bool isUnetModel)
661+
{
662+
if (!isUnetModel)
600663
return InferenceWorkflowProfile.DefaultCheckpoint;
601664

602-
var model = SelectedUnetModel;
603665
var baseModel = model?.Local?.ConnectedModelInfo?.BaseModel;
604666

605667
if (!string.IsNullOrWhiteSpace(baseModel))
@@ -636,7 +698,11 @@ private InferenceWorkflowProfile InferWorkflowProfile()
636698
: InferenceWorkflowProfile.ZImageBase;
637699
}
638700

639-
if (name.Contains("flux2", StringComparison.OrdinalIgnoreCase))
701+
if (
702+
name.Contains("flux2", StringComparison.OrdinalIgnoreCase)
703+
|| name.Contains("flux-2", StringComparison.OrdinalIgnoreCase)
704+
|| name.Contains("flux_2", StringComparison.OrdinalIgnoreCase)
705+
)
640706
return InferenceWorkflowProfile.Flux2;
641707

642708
if (name.Contains("flux", StringComparison.OrdinalIgnoreCase))
@@ -651,6 +717,44 @@ private InferenceWorkflowProfile InferWorkflowProfile()
651717
return InferenceWorkflowProfile.DefaultCheckpoint;
652718
}
653719

720+
private IReadOnlyList<HybridModelFile> GetWorkflowFilteredModels()
721+
{
722+
var allModels = ClientManager.AllModels.ToList();
723+
724+
if (SelectedWorkflowProfile is InferenceWorkflowProfile.Auto or InferenceWorkflowProfile.Custom)
725+
return allModels;
726+
727+
var filteredModels = allModels
728+
.Where(model => IsModelCompatibleWithWorkflow(model, SelectedWorkflowProfile))
729+
.ToList();
730+
731+
if (filteredModels.Count == 0)
732+
return allModels;
733+
734+
if (
735+
SelectedUnifiedModel is { } selected
736+
&& filteredModels.All(model => !HybridModelFile.Comparer.Equals(model, selected))
737+
)
738+
{
739+
filteredModels.Insert(0, selected);
740+
}
741+
742+
return filteredModels;
743+
}
744+
745+
private static bool IsModelCompatibleWithWorkflow(HybridModelFile model, InferenceWorkflowProfile profile)
746+
{
747+
var isUnetModel = model.Local?.SharedFolderType is SharedFolderType.DiffusionModels;
748+
749+
if (profile is InferenceWorkflowProfile.DefaultCheckpoint)
750+
return !isUnetModel;
751+
752+
if (!isUnetModel)
753+
return false;
754+
755+
return InferWorkflowProfile(model, true) == profile;
756+
}
757+
654758
/// <summary>
655759
/// Loads text encoders from the saved model state, supporting both new and legacy formats.
656760
/// </summary>
@@ -870,6 +974,10 @@ private void RefreshWorkflowProfileState()
870974
OnPropertyChanged(nameof(ShowShift));
871975
OnPropertyChanged(nameof(ShowEncoderTypeSelection));
872976
OnPropertyChanged(nameof(HasRecommendedDefaults));
977+
OnPropertyChanged(nameof(WorkflowFilteredModels));
978+
OnPropertyChanged(nameof(WorkflowProfileStatusText));
979+
OnPropertyChanged(nameof(ShowWorkflowProfileStatus));
980+
OnPropertyChanged(nameof(RecommendedDefaultsToolTip));
873981

874982
if (!isLoadingState)
875983
{

0 commit comments

Comments
 (0)