Implement bulk operations for LoRAs: add send to workflow and bulk delete functionality with modal confirmation.

This commit is contained in:
Will Miao
2025-06-03 07:44:52 +08:00
parent 99d2ba26b9
commit e151a19fcf
5 changed files with 182 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
import { state } from '../state/index.js';
import { showToast, copyToClipboard } from '../utils/uiHelpers.js';
import { showToast, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js';
import { updateCardsForBulkMode } from '../components/LoraCard.js';
import { modalManager } from './ModalManager.js';
export class BulkManager {
constructor() {
@@ -208,6 +209,131 @@ export class BulkManager {
await copyToClipboard(loraSyntaxes.join(', '), `Copied ${loraSyntaxes.length} LoRA syntaxes to clipboard`);
}
// Add method to send all selected loras to workflow
async sendAllLorasToWorkflow() {
if (state.selectedLoras.size === 0) {
showToast('No LoRAs selected', 'warning');
return;
}
const loraSyntaxes = [];
const missingLoras = [];
// Process all selected loras using our metadata cache
for (const filepath of state.selectedLoras) {
const metadata = state.loraMetadataCache.get(filepath);
if (metadata) {
const usageTips = JSON.parse(metadata.usageTips || '{}');
const strength = usageTips.strength || 1;
loraSyntaxes.push(`<lora:${metadata.fileName}:${strength}>`);
} else {
// If we don't have metadata, this is an error case
missingLoras.push(filepath);
}
}
// Handle any loras with missing metadata
if (missingLoras.length > 0) {
console.warn('Missing metadata for some selected loras:', missingLoras);
showToast(`Missing data for ${missingLoras.length} LoRAs`, 'warning');
}
if (loraSyntaxes.length === 0) {
showToast('No valid LoRAs to send', 'error');
return;
}
// Send the loras to the workflow
await sendLoraToWorkflow(loraSyntaxes.join(', '), false, 'lora');
}
// Show the bulk delete confirmation modal
showBulkDeleteModal() {
if (state.selectedLoras.size === 0) {
showToast('No LoRAs selected', 'warning');
return;
}
// Update the count in the modal
const countElement = document.getElementById('bulkDeleteCount');
if (countElement) {
countElement.textContent = state.selectedLoras.size;
}
// Show the modal
modalManager.showModal('bulkDeleteModal');
}
// Confirm bulk delete action
async confirmBulkDelete() {
if (state.selectedLoras.size === 0) {
showToast('No LoRAs selected', 'warning');
modalManager.closeModal('bulkDeleteModal');
return;
}
// Close the modal first before showing loading indicator
modalManager.closeModal('bulkDeleteModal');
try {
// Show loading indicator
state.loadingManager.showSimpleLoading('Deleting models...');
// Gather all file paths for deletion
const filePaths = Array.from(state.selectedLoras);
// Call the backend API
const response = await fetch('/api/loras/bulk-delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_paths: filePaths
})
});
const result = await response.json();
if (result.success) {
showToast(`Successfully deleted ${result.deleted_count} models`, 'success');
// If virtual scroller exists, update the UI without page reload
if (state.virtualScroller) {
// Remove each deleted item from the virtual scroller
filePaths.forEach(path => {
state.virtualScroller.removeItemByFilePath(path);
});
// Clear the selection
this.clearSelection();
} else {
// Clear the selection
this.clearSelection();
// Fall back to page reload for non-virtual scroll mode
setTimeout(() => {
window.location.reload();
}, 1500);
}
if (window.modelDuplicatesManager) {
// Update duplicates badge after refresh
window.modelDuplicatesManager.updateDuplicatesBadgeAfterRefresh();
}
} else {
showToast(`Error: ${result.error || 'Failed to delete models'}`, 'error');
}
} catch (error) {
console.error('Error during bulk delete:', error);
showToast('Failed to delete models', 'error');
} finally {
// Hide loading indicator
state.loadingManager.hide();
}
}
// Create and show the thumbnail strip of selected LoRAs
toggleThumbnailStrip() {
// If no items are selected, do nothing

View File

@@ -195,6 +195,18 @@ export class ModalManager {
});
}
// Add bulkDeleteModal registration
const bulkDeleteModal = document.getElementById('bulkDeleteModal');
if (bulkDeleteModal) {
this.registerModal('bulkDeleteModal', {
element: bulkDeleteModal,
onClose: () => {
this.getModal('bulkDeleteModal').element.classList.remove('show');
document.body.classList.remove('modal-open');
}
});
}
// Set up event listeners for modal toggles
const supportToggle = document.getElementById('supportToggleBtn');
if (supportToggle) {
@@ -275,10 +287,17 @@ export class ModalManager {
// Store current scroll position before showing modal
this.scrollPosition = window.scrollY;
if (id === 'deleteModal' || id === 'excludeModal' || id === 'duplicateDeleteModal' || id === 'modelDuplicateDeleteModal' || id === 'clearCacheModal') {
modal.element.classList.add('show');
if (
id === "deleteModal" ||
id === "excludeModal" ||
id === "duplicateDeleteModal" ||
id === "modelDuplicateDeleteModal" ||
id === "clearCacheModal" ||
id === "bulkDeleteModal"
) {
modal.element.classList.add("show");
} else {
modal.element.style.display = 'block';
modal.element.style.display = "block";
}
modal.isOpen = true;