1515using StabilityMatrix . Avalonia . Services ;
1616using StabilityMatrix . Avalonia . ViewModels . Base ;
1717using StabilityMatrix . Core . Api ;
18+ using StabilityMatrix . Core . Api . LykosAuthApi ;
1819using StabilityMatrix . Core . Attributes ;
1920using StabilityMatrix . Core . Database ;
2021using StabilityMatrix . Core . Extensions ;
@@ -30,28 +31,29 @@ namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
3031public partial class RecommendedModelsViewModel : ContentDialogViewModelBase
3132{
3233 private readonly ILogger < RecommendedModelsViewModel > logger ;
33- private readonly ILykosAuthApiV1 lykosApi ;
34+ private readonly IRecommendedModelsApi lykosApi ;
3435 private readonly ICivitApi civitApi ;
3536 private readonly ILiteDbContext liteDbContext ;
3637 private readonly ISettingsManager settingsManager ;
3738 private readonly INotificationService notificationService ;
3839 private readonly ITrackedDownloadService trackedDownloadService ;
3940 private readonly IDownloadService downloadService ;
4041 private readonly IModelImportService modelImportService ;
41- public SourceCache < RecommendedModelItemViewModel , int > CivitModels { get ; } = new ( p => p . ModelVersion . Id ) ;
4242
43- public IObservableCollection < RecommendedModelItemViewModel > Sd15Models { get ; set ; } =
44- new ObservableCollectionExtended < RecommendedModelItemViewModel > ( ) ;
43+ // Single source cache for all models
44+ public SourceCache < RecommendedModelItemViewModel , int > AllRecommendedModelsCache { get ; } =
45+ new ( p => p . ModelVersion . Id ) ;
4546
46- public IObservableCollection < RecommendedModelItemViewModel > SdxlModels { get ; } =
47+ // Single observable collection bound to the cache
48+ public IObservableCollection < RecommendedModelItemViewModel > RecommendedModels { get ; } =
4749 new ObservableCollectionExtended < RecommendedModelItemViewModel > ( ) ;
4850
4951 [ ObservableProperty ]
5052 private bool isLoading ;
5153
5254 public RecommendedModelsViewModel (
5355 ILogger < RecommendedModelsViewModel > logger ,
54- ILykosAuthApiV1 lykosApi ,
56+ IRecommendedModelsApi lykosApi ,
5557 ICivitApi civitApi ,
5658 ILiteDbContext liteDbContext ,
5759 ISettingsManager settingsManager ,
@@ -71,20 +73,12 @@ IModelImportService modelImportService
7173 this . downloadService = downloadService ;
7274 this . modelImportService = modelImportService ;
7375
74- CivitModels
75- . Connect ( )
76- . DeferUntilLoaded ( )
77- . Filter ( f => f . ModelVersion . BaseModel == "SD 1.5" )
78- . Bind ( Sd15Models )
79- . ObserveOn ( SynchronizationContext . Current )
80- . Subscribe ( ) ;
81-
82- CivitModels
76+ // Bind the single collection to the cache
77+ AllRecommendedModelsCache
8378 . Connect ( )
8479 . DeferUntilLoaded ( )
85- . Filter ( f => f . ModelVersion . BaseModel == "SDXL 1.0" || f . ModelVersion . BaseModel == "Pony" )
86- . Bind ( SdxlModels )
87- . ObserveOn ( SynchronizationContext . Current )
80+ . Bind ( RecommendedModels )
81+ . ObserveOn ( SynchronizationContext . Current ! ) // Use Current! if nullability context allows
8882 . Subscribe ( ) ;
8983 }
9084
@@ -94,69 +88,168 @@ public override async Task OnLoadedAsync()
9488 return ;
9589
9690 IsLoading = true ;
91+ AllRecommendedModelsCache . Clear ( ) ; // Clear cache before loading
9792
9893 try
9994 {
100- var recommendedModels = await lykosApi . GetRecommendedModels ( ) ;
101-
102- CivitModels . AddOrUpdate (
103- recommendedModels . Items . Select (
104- model =>
105- new RecommendedModelItemViewModel
106- {
107- ModelVersion = model . ModelVersions . First (
108- x =>
109- ! x . BaseModel . Contains ( "Turbo" , StringComparison . OrdinalIgnoreCase )
110- && ! x . BaseModel . Contains ( "Lightning" , StringComparison . OrdinalIgnoreCase )
111- ) ,
112- Author = $ "by { model . Creator ? . Username } ",
113- CivitModel = model
114- }
115- )
95+ // Call the V2 endpoint
96+ var recommendedModelsResponse = await lykosApi . GetRecommendedModels ( ) ;
97+
98+ var allModels = recommendedModelsResponse
99+ . RecommendedModelsByCategory . SelectMany ( kvp => kvp . Value ) // Flatten the dictionary values (lists of models)
100+ . DistinctBy ( m => m . Id ) // Ensure models appearing in multiple categories are only added once
101+ . Select ( model =>
102+ {
103+ // Find the first non-Turbo/Lightning version, or default to the first version if none match
104+ var suitableVersion =
105+ model . ModelVersions ? . FirstOrDefault (
106+ x =>
107+ ! x . BaseModel . Contains ( "Turbo" , StringComparison . OrdinalIgnoreCase )
108+ && ! x . BaseModel . Contains ( "Lightning" , StringComparison . OrdinalIgnoreCase )
109+ && x . Files != null
110+ && x . Files . Any ( f => f . Type == CivitFileType . Model ) // Ensure there's a model file
111+ )
112+ ?? model . ModelVersions ? . FirstOrDefault (
113+ x => x . Files != null && x . Files . Any ( f => f . Type == CivitFileType . Model )
114+ ) ;
115+
116+ if ( suitableVersion == null )
117+ {
118+ logger . LogWarning (
119+ "Model {ModelName} (ID: {ModelId}) has no suitable model version file." ,
120+ model . Name ,
121+ model . Id
122+ ) ;
123+ return null ; // Skip this model if no suitable version found
124+ }
125+
126+ return new RecommendedModelItemViewModel
127+ {
128+ ModelVersion = suitableVersion ,
129+ Author = $ "by { model . Creator ? . Username } ",
130+ CivitModel = model
131+ } ;
132+ } )
133+ . Where ( vm => vm != null ) ; // Filter out nulls (models skipped due to no suitable version)
134+
135+ AllRecommendedModelsCache . AddOrUpdate ( allModels ) ;
136+ }
137+ catch ( ApiException apiEx )
138+ {
139+ logger . LogError (
140+ apiEx ,
141+ "API Error fetching recommended models V2. Status: {StatusCode}" ,
142+ apiEx . StatusCode
143+ ) ;
144+ notificationService . Show (
145+ "Failed to get recommended models" ,
146+ $ "Could not reach the server. Please try again later. Error: { apiEx . StatusCode } "
116147 ) ;
148+ OnCloseButtonClick ( ) ;
117149 }
118150 catch ( Exception e )
119151 {
120- // hide dialog and show error msg
121- logger . LogError ( e , "Failed to get recommended models" ) ;
152+ logger . LogError ( e , "Failed to get recommended models V2" ) ;
122153 notificationService . Show (
123154 "Failed to get recommended models" ,
124- "Please try again later or check the Model Browser tab for more models ."
155+ "An unexpected error occurred. Please try again later or check the Model Browser tab."
125156 ) ;
126157 OnCloseButtonClick ( ) ;
127158 }
128-
129- IsLoading = false ;
159+ finally
160+ {
161+ IsLoading = false ;
162+ }
130163 }
131164
132165 [ RelayCommand ]
133166 private async Task DoImport ( )
134167 {
135- var selectedModels = SdxlModels . Where ( x => x . IsSelected ) . Concat ( Sd15Models . Where ( x => x . IsSelected ) ) ;
168+ var selectedModels = RecommendedModels . Where ( x => x . IsSelected ) . ToList ( ) ; // Use the single list
169+
170+ if ( ! selectedModels . Any ( ) )
171+ {
172+ notificationService . Show ( "No Models Selected" , "Please select at least one model to import." ) ;
173+ return ;
174+ }
175+
176+ IsLoading = true ; // Optionally show loading indicator during import
177+
178+ int successCount = 0 ;
179+ int failCount = 0 ;
136180
137181 foreach ( var model in selectedModels )
138182 {
139- // Get latest version file
140- var modelFile = model . ModelVersion . Files ? . FirstOrDefault (
141- x => x is { Type : CivitFileType . Model , IsPrimary : true }
142- ) ;
183+ // Get latest version file that is a Model type and marked primary, or fallback to first model file
184+ var modelFile =
185+ model . ModelVersion . Files ? . FirstOrDefault (
186+ f => f is { Type : CivitFileType . Model , IsPrimary : true }
187+ ) ?? model . ModelVersion . Files ? . FirstOrDefault ( f => f . Type == CivitFileType . Model ) ;
188+
143189 if ( modelFile is null )
144190 {
145- continue ;
191+ logger . LogWarning (
192+ "Skipping import for {ModelName}: No suitable model file found in version {VersionId}." ,
193+ model . CivitModel . Name ,
194+ model . ModelVersion . Id
195+ ) ;
196+ failCount ++ ;
197+ continue ; // Skip if no suitable file
146198 }
147199
148- var rootModelsDirectory = new DirectoryPath ( settingsManager . ModelsDirectory ) ;
200+ try
201+ {
202+ var rootModelsDirectory = new DirectoryPath ( settingsManager . ModelsDirectory ) ;
203+ var downloadDirectory = rootModelsDirectory . JoinDir (
204+ model . CivitModel . Type . ConvertTo < SharedFolderType > ( ) . GetStringValue ( )
205+ ) ;
206+
207+ await modelImportService . DoImport (
208+ model . CivitModel ,
209+ downloadDirectory ,
210+ model . ModelVersion ,
211+ modelFile
212+ ) ;
213+ successCount ++ ;
214+ model . IsSelected = false ; // De-select after successful import start
215+ }
216+ catch ( Exception ex )
217+ {
218+ logger . LogError ( ex , "Failed to initiate import for model {ModelName}" , model . CivitModel . Name ) ;
219+ failCount ++ ;
220+ // Consider notifying the user about the specific failure
221+ notificationService . Show (
222+ "Import Failed" ,
223+ $ "Could not start import for { model . CivitModel . Name } ."
224+ ) ;
225+ }
226+ }
149227
150- var downloadDirectory = rootModelsDirectory . JoinDir (
151- model . CivitModel . Type . ConvertTo < SharedFolderType > ( ) . GetStringValue ( )
152- ) ;
228+ IsLoading = false ; // Hide loading indicator
153229
154- await modelImportService . DoImport (
155- model . CivitModel ,
156- downloadDirectory ,
157- model . ModelVersion ,
158- modelFile
230+ if ( failCount == 0 && successCount > 0 )
231+ {
232+ notificationService . Show (
233+ "Import Started" ,
234+ $ "{ successCount } model(s) added to the download queue."
235+ ) ;
236+ // Optionally close the dialog after successful import initiation
237+ // OnCloseButtonClick();
238+ }
239+ else if ( successCount > 0 )
240+ {
241+ notificationService . Show (
242+ "Import Partially Started" ,
243+ $ "{ successCount } model(s) added to queue. { failCount } failed to start."
244+ ) ;
245+ }
246+ else if ( failCount > 0 )
247+ {
248+ notificationService . Show (
249+ "Import Failed" ,
250+ $ "Could not start import for { failCount } selected model(s)."
159251 ) ;
160252 }
253+ // else: No models were actually selected or processed, already handled.
161254 }
162255}
0 commit comments