mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
Implement bulk operations for LoRAs: add send to workflow and bulk delete functionality with modal confirmation.
This commit is contained in:
@@ -60,6 +60,18 @@
|
|||||||
border-color: var(--lora-accent);
|
border-color: var(--lora-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Danger button style - updated to use proper theme variables */
|
||||||
|
.bulk-operations-actions button.danger-btn {
|
||||||
|
background: oklch(70% 0.2 29); /* Light red background that works in both themes */
|
||||||
|
color: oklch(98% 0.01 0); /* Almost white text for good contrast */
|
||||||
|
border-color: var(--lora-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-actions button.danger-btn:hover {
|
||||||
|
background: var(--lora-error);
|
||||||
|
color: oklch(100% 0 0); /* Pure white text on hover for maximum contrast */
|
||||||
|
}
|
||||||
|
|
||||||
/* Style for selected cards */
|
/* Style for selected cards */
|
||||||
.lora-card.selected {
|
.lora-card.selected {
|
||||||
box-shadow: 0 0 0 2px var(--lora-accent);
|
box-shadow: 0 0 0 2px var(--lora-accent);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { state } from '../state/index.js';
|
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 { updateCardsForBulkMode } from '../components/LoraCard.js';
|
||||||
|
import { modalManager } from './ModalManager.js';
|
||||||
|
|
||||||
export class BulkManager {
|
export class BulkManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -208,6 +209,131 @@ export class BulkManager {
|
|||||||
await copyToClipboard(loraSyntaxes.join(', '), `Copied ${loraSyntaxes.length} LoRA syntaxes to clipboard`);
|
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
|
// Create and show the thumbnail strip of selected LoRAs
|
||||||
toggleThumbnailStrip() {
|
toggleThumbnailStrip() {
|
||||||
// If no items are selected, do nothing
|
// If no items are selected, do nothing
|
||||||
|
|||||||
@@ -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
|
// Set up event listeners for modal toggles
|
||||||
const supportToggle = document.getElementById('supportToggleBtn');
|
const supportToggle = document.getElementById('supportToggleBtn');
|
||||||
if (supportToggle) {
|
if (supportToggle) {
|
||||||
@@ -275,10 +287,17 @@ export class ModalManager {
|
|||||||
// Store current scroll position before showing modal
|
// Store current scroll position before showing modal
|
||||||
this.scrollPosition = window.scrollY;
|
this.scrollPosition = window.scrollY;
|
||||||
|
|
||||||
if (id === 'deleteModal' || id === 'excludeModal' || id === 'duplicateDeleteModal' || id === 'modelDuplicateDeleteModal' || id === 'clearCacheModal') {
|
if (
|
||||||
modal.element.classList.add('show');
|
id === "deleteModal" ||
|
||||||
|
id === "excludeModal" ||
|
||||||
|
id === "duplicateDeleteModal" ||
|
||||||
|
id === "modelDuplicateDeleteModal" ||
|
||||||
|
id === "clearCacheModal" ||
|
||||||
|
id === "bulkDeleteModal"
|
||||||
|
) {
|
||||||
|
modal.element.classList.add("show");
|
||||||
} else {
|
} else {
|
||||||
modal.element.style.display = 'block';
|
modal.element.style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.isOpen = true;
|
modal.isOpen = true;
|
||||||
|
|||||||
@@ -107,12 +107,18 @@
|
|||||||
0 selected <i class="fas fa-caret-down dropdown-caret"></i>
|
0 selected <i class="fas fa-caret-down dropdown-caret"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="bulk-operations-actions">
|
<div class="bulk-operations-actions">
|
||||||
|
<button onclick="bulkManager.sendAllLorasToWorkflow()" title="Send all selected LoRAs to workflow">
|
||||||
|
<i class="fas fa-arrow-right"></i> Send to Workflow
|
||||||
|
</button>
|
||||||
<button onclick="bulkManager.copyAllLorasSyntax()" title="Copy all selected LoRAs syntax">
|
<button onclick="bulkManager.copyAllLorasSyntax()" title="Copy all selected LoRAs syntax">
|
||||||
<i class="fas fa-copy"></i> Copy All
|
<i class="fas fa-copy"></i> Copy All
|
||||||
</button>
|
</button>
|
||||||
<button onclick="moveManager.showMoveModal('bulk')" title="Move selected LoRAs to folder">
|
<button onclick="moveManager.showMoveModal('bulk')" title="Move selected LoRAs to folder">
|
||||||
<i class="fas fa-folder-open"></i> Move All
|
<i class="fas fa-folder-open"></i> Move All
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="bulkManager.showBulkDeleteModal()" title="Delete selected LoRAs" class="danger-btn">
|
||||||
|
<i class="fas fa-trash"></i> Delete All
|
||||||
|
</button>
|
||||||
<button onclick="bulkManager.clearSelection()" title="Clear selection">
|
<button onclick="bulkManager.clearSelection()" title="Clear selection">
|
||||||
<i class="fas fa-times"></i> Clear
|
<i class="fas fa-times"></i> Clear
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -69,6 +69,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bulk Delete Confirmation Modal -->
|
||||||
|
<div id="bulkDeleteModal" class="modal delete-modal">
|
||||||
|
<div class="modal-content delete-modal-content">
|
||||||
|
<h2>Delete Multiple Models</h2>
|
||||||
|
<p class="delete-message">Are you sure you want to delete all selected models and their associated files?</p>
|
||||||
|
<div class="delete-model-info">
|
||||||
|
<p><span id="bulkDeleteCount">0</span> models will be permanently deleted.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="cancel-btn" onclick="modalManager.closeModal('bulkDeleteModal')">Cancel</button>
|
||||||
|
<button class="delete-btn" onclick="bulkManager.confirmBulkDelete()">Delete All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Settings Modal -->
|
<!-- Settings Modal -->
|
||||||
<div id="settingsModal" class="modal">
|
<div id="settingsModal" class="modal">
|
||||||
<div class="modal-content settings-modal">
|
<div class="modal-content settings-modal">
|
||||||
|
|||||||
Reference in New Issue
Block a user