mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-28 08:21:27 -03:00
fix(recipes): restore batch import modal on reopen and log recipe ingest progress (#1084)
This commit is contained in:
@@ -17,17 +17,77 @@ export class BatchImportManager {
|
||||
this.progress = null;
|
||||
this.results = null;
|
||||
this.isCancelled = false;
|
||||
this.isImporting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the batch import modal
|
||||
* Show the batch import modal.
|
||||
*
|
||||
* If an import is still running in the background (e.g. the modal was
|
||||
* closed mid-run with the X button or a backdrop click), reopen it in the
|
||||
* progress/results view instead of resetting to a fresh form, so the modal
|
||||
* never becomes unusable while an operation is in flight.
|
||||
*/
|
||||
showModal() {
|
||||
if (!this.initialized) {
|
||||
this.initialize();
|
||||
}
|
||||
this.resetState();
|
||||
modalManager.showModal('batchImportModal');
|
||||
|
||||
if (this.isImporting && this.operationId) {
|
||||
console.log(
|
||||
`[BatchImport] Reopening modal while operation ${this.operationId} is still active; restoring its view.`
|
||||
);
|
||||
this.resumeRunningImportView();
|
||||
} else if (this.results && this.operationId) {
|
||||
// A previous operation finished while the modal was closed —
|
||||
// restore its results view instead of discarding them.
|
||||
console.log(
|
||||
`[BatchImport] Reopening modal after operation ${this.operationId} finished; showing results.`
|
||||
);
|
||||
this.showStep('batchResultsStep');
|
||||
this.updateResultsUI(this.results);
|
||||
} else {
|
||||
this.resetState();
|
||||
console.log('[BatchImport] Opening batch import modal.');
|
||||
}
|
||||
|
||||
modalManager.showModal('batchImportModal', null, () => this.handleModalClosed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the progress (or results) view for an operation that is still
|
||||
* running in the background after the modal was closed.
|
||||
*/
|
||||
resumeRunningImportView() {
|
||||
// Operation completed while the modal was closed — show results
|
||||
if (this.results) {
|
||||
this.showStep('batchResultsStep');
|
||||
this.updateResultsUI(this.results);
|
||||
return;
|
||||
}
|
||||
|
||||
// Still running — restore the progress step and re-attach live updates
|
||||
this.showStep('batchProgressStep');
|
||||
this.updateProgressUI(this.progress || {});
|
||||
if (!this.wsConnection && !this.pollingInterval) {
|
||||
this.connectWebSocket();
|
||||
this.startPolling();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the modal is closed (X button, backdrop click, cancel,
|
||||
* closeAndReset). Logs whether an operation is still running so users can
|
||||
* tell from the console that work continues in the background.
|
||||
*/
|
||||
handleModalClosed() {
|
||||
if (this.isImporting && this.operationId) {
|
||||
console.log(
|
||||
`[BatchImport] Modal closed while import ${this.operationId} is still running; it keeps running in the background. Reopen the modal to watch its progress.`
|
||||
);
|
||||
} else {
|
||||
console.log('[BatchImport] Modal closed (no active import).');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +117,7 @@ export class BatchImportManager {
|
||||
this.progress = null;
|
||||
this.results = null;
|
||||
this.isCancelled = false;
|
||||
this.isImporting = false;
|
||||
|
||||
// Reset UI
|
||||
this.showStep('batchInputStep');
|
||||
@@ -172,6 +233,10 @@ export class BatchImportManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[BatchImport] Starting import: mode=${data.mode}, items=${data.items ? data.items.length : 'directory'}, tags=${data.tags.length}`
|
||||
);
|
||||
|
||||
try {
|
||||
// Show progress step
|
||||
this.showStep('batchProgressStep');
|
||||
@@ -182,6 +247,8 @@ export class BatchImportManager {
|
||||
if (response.success) {
|
||||
this.operationId = response.operation_id;
|
||||
this.isCancelled = false;
|
||||
this.isImporting = true;
|
||||
console.log(`[BatchImport] Import started, operation_id=${this.operationId}`);
|
||||
|
||||
// Connect to WebSocket for real-time updates
|
||||
this.connectWebSocket();
|
||||
@@ -189,6 +256,7 @@ export class BatchImportManager {
|
||||
// Start polling as fallback
|
||||
this.startPolling();
|
||||
} else {
|
||||
console.warn(`[BatchImport] Failed to start import: ${response.error}`);
|
||||
showToast('toast.recipes.batchImportFailed', { message: response.error }, 'error');
|
||||
this.showStep('batchInputStep');
|
||||
}
|
||||
@@ -357,6 +425,16 @@ export class BatchImportManager {
|
||||
handleProgressUpdate(progress) {
|
||||
this.progress = progress;
|
||||
this.updateProgressUI(progress);
|
||||
|
||||
// Console visibility for background progress: while the modal is
|
||||
// closed (or open), the console shows what the import is doing.
|
||||
console.log(
|
||||
`[BatchImport] Progress ${Math.round(progress.progress_percent || 0)}% ` +
|
||||
`(${progress.completed}/${progress.total}) ` +
|
||||
`status=${progress.status} ` +
|
||||
`success=${progress.success} failed=${progress.failed} skipped=${progress.skipped} ` +
|
||||
`item=${progress.current_item || '-'}`
|
||||
);
|
||||
|
||||
// Check if import is complete
|
||||
if (progress.status === 'completed' || progress.status === 'cancelled' ||
|
||||
@@ -431,7 +509,12 @@ export class BatchImportManager {
|
||||
*/
|
||||
importComplete(progress) {
|
||||
this.cleanupConnections();
|
||||
this.isImporting = false;
|
||||
this.results = progress;
|
||||
console.log(
|
||||
`[BatchImport] Import finished: status=${progress.status} ` +
|
||||
`total=${progress.total} success=${progress.success} failed=${progress.failed} skipped=${progress.skipped}`
|
||||
);
|
||||
|
||||
// Refresh recipes list to show newly imported recipes
|
||||
if (window.recipeManager && typeof window.recipeManager.loadRecipes === 'function') {
|
||||
@@ -559,6 +642,7 @@ export class BatchImportManager {
|
||||
if (!this.operationId) return;
|
||||
|
||||
this.isCancelled = true;
|
||||
console.log(`[BatchImport] Cancelling import ${this.operationId}...`);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/lm/recipes/batch-import/cancel', {
|
||||
@@ -572,8 +656,10 @@ export class BatchImportManager {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
console.log(`[BatchImport] Cancel request accepted for ${this.operationId}`);
|
||||
showToast('toast.recipes.batchImportCancelling', {}, 'info');
|
||||
} else {
|
||||
console.warn(`[BatchImport] Cancel request failed: ${data.error}`);
|
||||
showToast('toast.recipes.batchImportCancelFailed', { message: data.error }, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -586,6 +672,7 @@ export class BatchImportManager {
|
||||
* Close modal and reset state
|
||||
*/
|
||||
closeAndReset() {
|
||||
console.log('[BatchImport] Closing modal and resetting state.');
|
||||
this.cleanupConnections();
|
||||
this.resetState();
|
||||
modalManager.closeModal('batchImportModal');
|
||||
@@ -595,6 +682,7 @@ export class BatchImportManager {
|
||||
* Start a new import (from results step)
|
||||
*/
|
||||
startNewImport() {
|
||||
console.log('[BatchImport] Starting a new import from the results view.');
|
||||
this.resetState();
|
||||
this.showStep('batchInputStep');
|
||||
}
|
||||
@@ -789,6 +877,14 @@ export class BatchImportManager {
|
||||
* Clean up WebSocket and polling connections
|
||||
*/
|
||||
cleanupConnections() {
|
||||
const hasWs = this.wsConnection && (
|
||||
this.wsConnection.readyState === WebSocket.OPEN ||
|
||||
this.wsConnection.readyState === WebSocket.CONNECTING
|
||||
);
|
||||
if (hasWs || this.pollingInterval) {
|
||||
console.log('[BatchImport] Cleaning up live connections (WebSocket/polling).');
|
||||
}
|
||||
|
||||
if (this.wsConnection) {
|
||||
if (this.wsConnection.readyState === WebSocket.OPEN ||
|
||||
this.wsConnection.readyState === WebSocket.CONNECTING) {
|
||||
|
||||
@@ -66,10 +66,15 @@ export class ImportManager {
|
||||
|
||||
// Show modal
|
||||
modalManager.showModal('importModal', null, () => {
|
||||
console.log('[RecipeImport] Import modal closed.');
|
||||
this.cleanupFolderBrowser();
|
||||
this.stepManager.removeInjectedStyles();
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[RecipeImport] Import modal opened (${recipeData ? 'download-missing-loras mode' : 'new import'}).`
|
||||
);
|
||||
|
||||
// Verify visibility and focus on the URL input (primary mode)
|
||||
setTimeout(() => {
|
||||
const urlInput = document.getElementById('imageUrlInput');
|
||||
|
||||
@@ -146,7 +146,13 @@ export class ModalManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Add batchImportModal registration
|
||||
// Add batchImportModal registration.
|
||||
// Deliberately no closeOnOutsideClick: batch import is a stateful,
|
||||
// multi-step workflow (input -> progress -> results) that runs a
|
||||
// long-lived background operation. A stray backdrop click would
|
||||
// dismiss the modal while the import keeps running, leaving users
|
||||
// unable to tell what is still happening (issue #1084). Close is
|
||||
// available via the explicit X button / Cancel instead.
|
||||
const batchImportModal = document.getElementById('batchImportModal');
|
||||
if (batchImportModal) {
|
||||
this.registerModal('batchImportModal', {
|
||||
@@ -154,8 +160,7 @@ export class ModalManager {
|
||||
onClose: () => {
|
||||
this.getModal('batchImportModal').element.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
closeOnOutsideClick: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ export class DownloadManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[RecipeImport] Saving recipe "${this.importManager.recipeName}" (download-only=${isDownloadOnly}, skipDownload=${skipDownload})`
|
||||
);
|
||||
|
||||
try {
|
||||
// Show progress indicator
|
||||
const loadingMessage = skipDownload
|
||||
@@ -102,6 +106,7 @@ export class DownloadManager {
|
||||
if (!result.success) {
|
||||
// Handle save error
|
||||
console.error("Failed to save recipe:", result.error);
|
||||
console.log('[RecipeImport] Save failed; closing import modal.');
|
||||
showToast('toast.recipes.recipeSaveFailed', { error: result.error }, 'error');
|
||||
// Close modal
|
||||
modalManager.closeModal('importModal');
|
||||
@@ -112,6 +117,7 @@ export class DownloadManager {
|
||||
// Check if we need to download LoRAs (skip if skipDownload is true)
|
||||
let failedDownloads = 0;
|
||||
if (!skipDownload && this.importManager.downloadableLoRAs && this.importManager.downloadableLoRAs.length > 0) {
|
||||
console.log(`[RecipeImport] Downloading ${this.importManager.downloadableLoRAs.length} missing LoRA(s)...`);
|
||||
await this.downloadMissingLoras();
|
||||
}
|
||||
|
||||
@@ -127,6 +133,7 @@ export class DownloadManager {
|
||||
}
|
||||
|
||||
modalManager.closeModal('importModal');
|
||||
console.log(`[RecipeImport] Recipe "${this.importManager.recipeName}" saved successfully.`);
|
||||
|
||||
if (isDownloadOnly && state.virtualScroller) {
|
||||
const recipeId = this.importManager.recipeId;
|
||||
|
||||
@@ -30,6 +30,7 @@ export class ImageProcessor {
|
||||
errorElement.textContent = '';
|
||||
this.importManager.recipeImage = file;
|
||||
this.importManager.importMode = 'upload';
|
||||
console.log(`[RecipeImport] Recipe image selected: ${file.name}`);
|
||||
|
||||
// Show the selected file name in the drop zone
|
||||
this.importManager.updateSelectedFileName(file.name);
|
||||
@@ -66,6 +67,10 @@ export class ImageProcessor {
|
||||
errorElement.textContent = '';
|
||||
this.importManager.importMode = 'url';
|
||||
|
||||
console.log(
|
||||
`[RecipeImport] Analyzing recipe input (${input.startsWith('http://') || input.startsWith('https://') ? 'remote URL' : 'local path'}): ${input.slice(0, 80)}`
|
||||
);
|
||||
|
||||
// Put the fetch button into a loading state to prevent duplicate submits
|
||||
const fetchBtn = document.getElementById('fetchImageBtn');
|
||||
this._setFetchButtonLoading(fetchBtn, true);
|
||||
@@ -144,6 +149,9 @@ export class ImageProcessor {
|
||||
this.importManager.importAsNew = false;
|
||||
|
||||
// Proceed to recipe details step
|
||||
console.log(
|
||||
`[RecipeImport] Analysis complete: ${this.importManager.recipeData.loras.length} LoRA(s) found, ${this.importManager.missingLoras.length} missing locally.`
|
||||
);
|
||||
this.importManager.showRecipeDetailsStep();
|
||||
|
||||
} catch (error) {
|
||||
@@ -196,6 +204,9 @@ export class ImageProcessor {
|
||||
this.importManager.importAsNew = false;
|
||||
|
||||
// Proceed to recipe details step
|
||||
console.log(
|
||||
`[RecipeImport] Analysis complete: ${this.importManager.recipeData.loras.length} LoRA(s) found, ${this.importManager.missingLoras.length} missing locally.`
|
||||
);
|
||||
this.importManager.showRecipeDetailsStep();
|
||||
|
||||
} catch (error) {
|
||||
@@ -251,6 +262,9 @@ export class ImageProcessor {
|
||||
this.importManager.importAsNew = false;
|
||||
|
||||
// Proceed to recipe details step
|
||||
console.log(
|
||||
`[RecipeImport] Analysis complete: ${this.importManager.recipeData.loras.length} LoRA(s) found, ${this.importManager.missingLoras.length} missing locally.`
|
||||
);
|
||||
this.importManager.showRecipeDetailsStep();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user