From 836a64e728b3e7d259796a2dc3e4861d80f817bc Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 26 Jul 2025 23:45:57 +0800 Subject: [PATCH] refactor: enhance bulk metadata refresh functionality and update UI components --- static/css/components/modal/update-modal.css | 4 +- static/js/api/baseModelApi.js | 98 +++++++++++++++++++ static/js/managers/BulkManager.js | 50 ++++++++++ static/js/managers/LoadingManager.js | 24 ++++- templates/components/controls.html | 3 + templates/components/modals/update_modal.html | 2 +- 6 files changed, 177 insertions(+), 4 deletions(-) diff --git a/static/css/components/modal/update-modal.css b/static/css/components/modal/update-modal.css index 7645b751..e254a255 100644 --- a/static/css/components/modal/update-modal.css +++ b/static/css/components/modal/update-modal.css @@ -45,7 +45,7 @@ opacity: 0.8; } -.progress-bar { +.update-progress-bar { width: 100%; height: 8px; background-color: rgba(0, 0, 0, 0.1); @@ -53,7 +53,7 @@ overflow: hidden; } -[data-theme="dark"] .progress-bar { +[data-theme="dark"] .update-progress-bar { background-color: rgba(255, 255, 255, 0.1); } diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index f2e787f2..38b76616 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -477,6 +477,104 @@ class ModelApiClient { }); } + /** + * Fetch CivitAI metadata for multiple models with progress tracking + */ + async refreshBulkModelMetadata(filePaths) { + if (!filePaths || filePaths.length === 0) { + throw new Error('No file paths provided'); + } + + const totalItems = filePaths.length; + let processedCount = 0; + let successCount = 0; + let failedItems = []; + + const progressController = state.loadingManager.showEnhancedProgress('Starting metadata refresh...'); + + try { + // Process files sequentially to avoid overwhelming the API + for (let i = 0; i < filePaths.length; i++) { + const filePath = filePaths[i]; + const fileName = filePath.split('/').pop(); + + try { + const overallProgress = Math.floor((i / totalItems) * 100); + progressController.updateProgress( + overallProgress, + fileName, + `Processing ${i + 1}/${totalItems}: ${fileName}` + ); + + const response = await fetch(this.apiConfig.endpoints.fetchCivitai, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ file_path: filePath }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + if (data.success) { + if (data.metadata && state.virtualScroller) { + state.virtualScroller.updateSingleItem(filePath, data.metadata); + } + successCount++; + } else { + throw new Error(data.error || 'Failed to refresh metadata'); + } + + } catch (error) { + console.error(`Error refreshing metadata for ${fileName}:`, error); + failedItems.push({ filePath, fileName, error: error.message }); + } + + processedCount++; + } + + // Show completion message + let completionMessage; + if (successCount === totalItems) { + completionMessage = `Successfully refreshed all ${successCount} ${this.apiConfig.config.displayName}s`; + showToast(completionMessage, 'success'); + } else if (successCount > 0) { + completionMessage = `Refreshed ${successCount} of ${totalItems} ${this.apiConfig.config.displayName}s`; + showToast(completionMessage, 'warning'); + + if (failedItems.length > 0) { + const failureMessage = failedItems.length <= 3 + ? failedItems.map(item => `${item.fileName}: ${item.error}`).join('\n') + : failedItems.slice(0, 3).map(item => `${item.fileName}: ${item.error}`).join('\n') + + `\n(and ${failedItems.length - 3} more)`; + showToast(`Failed refreshes:\n${failureMessage}`, 'warning', 6000); + } + } else { + completionMessage = `Failed to refresh metadata for any ${this.apiConfig.config.displayName}s`; + showToast(completionMessage, 'error'); + } + + await progressController.complete(completionMessage); + + return { + success: successCount > 0, + total: totalItems, + processed: processedCount, + successful: successCount, + failed: failedItems.length, + errors: failedItems + }; + + } catch (error) { + console.error('Error in bulk metadata refresh:', error); + showToast(`Failed to refresh metadata: ${error.message}`, 'error'); + await progressController.complete('Operation failed'); + throw error; + } + } + /** * Move a single model to target path * @returns {string|null} - The new file path if moved, null if not moved diff --git a/static/js/managers/BulkManager.js b/static/js/managers/BulkManager.js index 2ce9b0b2..44d13144 100644 --- a/static/js/managers/BulkManager.js +++ b/static/js/managers/BulkManager.js @@ -2,6 +2,7 @@ import { state } from '../state/index.js'; import { showToast, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js'; import { updateCardsForBulkMode } from '../components/shared/ModelCard.js'; import { modalManager } from './ModalManager.js'; +import { getModelApiClient } from '../api/baseModelApi.js'; export class BulkManager { constructor() { @@ -560,6 +561,55 @@ export class BulkManager { this.updateThumbnailStrip(); } } + + // Add method to refresh metadata for all selected models + async refreshAllMetadata() { + if (state.selectedLoras.size === 0) { + showToast('No models selected', 'warning'); + return; + } + + try { + // Get the API client for the current model type + const apiClient = getModelApiClient(); + + // Convert Set to Array for processing + const filePaths = Array.from(state.selectedLoras); + + // Call the bulk refresh method + const result = await apiClient.refreshBulkModelMetadata(filePaths); + + if (result.success) { + // Update the metadata cache for successfully refreshed items + for (const filepath of state.selectedLoras) { + const metadata = state.loraMetadataCache.get(filepath); + if (metadata) { + // Find the corresponding card to get updated data + const card = document.querySelector(`.model-card[data-filepath="${filepath}"]`); + if (card) { + state.loraMetadataCache.set(filepath, { + ...metadata, + fileName: card.dataset.file_name, + usageTips: card.dataset.usage_tips, + previewUrl: this.getCardPreviewUrl(card), + isVideo: this.isCardPreviewVideo(card), + modelName: card.dataset.name + }); + } + } + } + + // Update thumbnail strip if visible + if (this.isStripVisible) { + this.updateThumbnailStrip(); + } + } + + } catch (error) { + console.error('Error during bulk metadata refresh:', error); + showToast('Failed to refresh metadata', 'error'); + } + } } // Create a singleton instance diff --git a/static/js/managers/LoadingManager.js b/static/js/managers/LoadingManager.js index 27ea52d5..924b6d4f 100644 --- a/static/js/managers/LoadingManager.js +++ b/static/js/managers/LoadingManager.js @@ -145,6 +145,28 @@ export class LoadingManager { } } + // Enhanced progress display without callback pattern + showEnhancedProgress(message = 'Processing...') { + this.show(message, 0); + + // Return update functions + return { + updateProgress: (percent, currentItem = '', statusMessage = '') => { + this.setProgress(percent); + if (statusMessage) { + this.setStatus(statusMessage); + } + }, + + complete: async (completionMessage = 'Complete') => { + this.setProgress(100); + this.setStatus(completionMessage); + await new Promise(resolve => setTimeout(resolve, 500)); + this.hide(); + } + }; + } + showSimpleLoading(message = 'Loading...') { this.overlay.style.display = 'flex'; this.progressBar.style.display = 'none'; @@ -154,4 +176,4 @@ export class LoadingManager { restoreProgressBar() { this.progressBar.style.display = 'block'; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/templates/components/controls.html b/templates/components/controls.html index 68f03db1..95b424de 100644 --- a/templates/components/controls.html +++ b/templates/components/controls.html @@ -125,6 +125,9 @@ + diff --git a/templates/components/modals/update_modal.html b/templates/components/modals/update_modal.html index e6e162ae..8cbae82c 100644 --- a/templates/components/modals/update_modal.html +++ b/templates/components/modals/update_modal.html @@ -35,7 +35,7 @@