refactor: enhance bulk metadata refresh functionality and update UI components

This commit is contained in:
Will Miao
2025-07-26 23:45:57 +08:00
parent 08ba0c9f42
commit 836a64e728
6 changed files with 177 additions and 4 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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';
}
}
}