mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
feat(recipes): show a summary modal after rematch runs
Replace the post-run toast cascade and the standalone L4 results modal with a summary modal modeled on the batch download summary: 3-state header, stat cards (matched / needs review / unresolved / errors), an L4 review table with per-entry undo, and a copyable report. Wired into the global, bulk and single-recipe rematch entries; complete no-op runs keep the lightweight toast. Obsolete results-modal code, styles and i18n keys are removed.
This commit is contained in:
@@ -4,6 +4,7 @@ import { handleUndoDelete } from '../utils/undoHelpers.js';
|
||||
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { rematchModalManager } from './RematchModalManager.js';
|
||||
import { showRematchSummary } from '../components/RematchSummaryModal.js';
|
||||
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
||||
import { RecipeSidebarApiClient, updateRecipeMetadata, extractRecipeId } from '../api/recipeApi.js';
|
||||
import { MODEL_TYPES, MODEL_CONFIG } from '../api/apiConfig.js';
|
||||
@@ -1025,47 +1026,32 @@ export class BulkManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedEntries > 0) {
|
||||
const hasFailures = failures > 0;
|
||||
const toastKey = hasFailures
|
||||
? 'toast.recipes.rematchCompleteErrors'
|
||||
: 'toast.recipes.rematchComplete';
|
||||
showToast(
|
||||
toastKey,
|
||||
{ rematched, skipped, total, entries: matchedEntries, recipes: matchedRecipes, failures },
|
||||
hasFailures ? 'warning' : 'success'
|
||||
);
|
||||
} else if (failures > 0) {
|
||||
// Nothing matched and at least one recipe errored —
|
||||
// "no rematch needed" would be actively misleading here.
|
||||
showToast(
|
||||
'toast.recipes.rematchAllFailed',
|
||||
{ total, failures },
|
||||
'error'
|
||||
);
|
||||
} else if (unresolvedEntries > 0) {
|
||||
// Entries existed but have no local model — expected for
|
||||
// models deleted from Civitai; informational, not an error.
|
||||
showToast(
|
||||
'toast.recipes.rematchUnmatched',
|
||||
{ entries: unresolvedEntries, recipes: unresolvedRecipes, total },
|
||||
'info'
|
||||
);
|
||||
} else {
|
||||
// Complete no-op (nothing matched, nothing unresolved, no
|
||||
// errors) keeps the lightweight toast; anything else opens
|
||||
// the post-run summary modal.
|
||||
const l4Matches = Array.isArray(result.l4_matches) ? result.l4_matches : [];
|
||||
const isNoop = matchedEntries === 0 && unresolvedEntries === 0 && failures === 0;
|
||||
if (isNoop) {
|
||||
showToast(
|
||||
'toast.recipes.rematchSkipped',
|
||||
{ total },
|
||||
'info'
|
||||
);
|
||||
} else {
|
||||
showRematchSummary({
|
||||
scope: 'bulk',
|
||||
total,
|
||||
matchedRecipes,
|
||||
matchedEntries,
|
||||
unresolvedRecipes,
|
||||
unresolvedEntries,
|
||||
skipped,
|
||||
errors: failures,
|
||||
l4Matches,
|
||||
});
|
||||
}
|
||||
|
||||
if (state.bulkMode) this.toggleBulkMode();
|
||||
|
||||
// Filename-level (L4) matches are imprecise — always surface
|
||||
// them for review/undo.
|
||||
if (Array.isArray(result.l4_matches) && result.l4_matches.length > 0) {
|
||||
rematchModalManager.showResultsModal(result.l4_matches);
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || 'Bulk rematch failed');
|
||||
}
|
||||
|
||||
@@ -360,19 +360,6 @@ export class ModalManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Register rematchResultsModal
|
||||
const rematchResultsModal = document.getElementById('rematchResultsModal');
|
||||
if (rematchResultsModal) {
|
||||
this.registerModal('rematchResultsModal', {
|
||||
element: rematchResultsModal,
|
||||
onClose: () => {
|
||||
this.getModal('rematchResultsModal').element.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
closeOnOutsideClick: true
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', this.boundHandleEscape);
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { translate } from '../utils/i18nHelpers.js';
|
||||
import { showToast } from '../utils/uiHelpers.js';
|
||||
|
||||
/**
|
||||
* Owns the two recipe-rematch modals:
|
||||
*
|
||||
* - rematchOptionsModal — shown BEFORE a global/bulk/single rematch run;
|
||||
* collects the "relaxed matching" opt-in and only then invokes the run
|
||||
* callback.
|
||||
* - rematchResultsModal — shown AFTER a run that produced L4 (filename
|
||||
* level) matches; lists them for review with a per-row Undo that calls
|
||||
* the existing restore endpoints.
|
||||
* Owns the recipe-rematch options modal (rematchOptionsModal), shown BEFORE
|
||||
* a global/bulk/single rematch run; collects the "relaxed matching" opt-in
|
||||
* and only then invokes the run callback. Post-run reporting lives in
|
||||
* static/js/components/RematchSummaryModal.js.
|
||||
*/
|
||||
export class RematchModalManager {
|
||||
constructor() {
|
||||
this._optionsConfirmCallback = null;
|
||||
this._resultsMatches = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,99 +67,6 @@ export class RematchModalManager {
|
||||
this._optionsConfirmCallback = null;
|
||||
modalManager.closeModal('rematchOptionsModal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the results modal listing L4 (filename-level) matches.
|
||||
*
|
||||
* @param {Array<{recipe_id: string, type: string, entry: string, file_name: string, lora_index?: number}>} l4Matches
|
||||
*/
|
||||
showResultsModal(l4Matches) {
|
||||
if (!Array.isArray(l4Matches) || l4Matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
const list = document.getElementById('rematchResultsList');
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
this._resultsMatches = l4Matches;
|
||||
list.innerHTML = '';
|
||||
|
||||
l4Matches.forEach((match, index) => {
|
||||
const row = document.createElement('li');
|
||||
row.className = 'rematch-results-row';
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'rematch-results-info';
|
||||
|
||||
const entryName = document.createElement('span');
|
||||
entryName.className = 'rematch-results-entry';
|
||||
entryName.textContent = match.entry || '';
|
||||
|
||||
const matchedFile = document.createElement('span');
|
||||
matchedFile.className = 'rematch-results-file';
|
||||
matchedFile.textContent = `→ ${match.file_name || ''}`;
|
||||
|
||||
const recipeRef = document.createElement('span');
|
||||
recipeRef.className = 'rematch-results-recipe';
|
||||
recipeRef.textContent = match.recipe_id || '';
|
||||
|
||||
info.appendChild(entryName);
|
||||
info.appendChild(matchedFile);
|
||||
info.appendChild(recipeRef);
|
||||
|
||||
const undoButton = document.createElement('button');
|
||||
undoButton.className = 'secondary-btn rematch-results-undo';
|
||||
undoButton.textContent = translate('modals.rematchResults.undo', {}, 'Undo');
|
||||
undoButton.addEventListener('click', () => this.undoMatch(index, row, undoButton));
|
||||
|
||||
row.appendChild(info);
|
||||
row.appendChild(undoButton);
|
||||
list.appendChild(row);
|
||||
});
|
||||
|
||||
modalManager.showModal('rematchResultsModal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo a single L4 match via the existing restore endpoints. On success
|
||||
* the row is struck through and its button disabled.
|
||||
*/
|
||||
async undoMatch(index, row, button) {
|
||||
const match = this._resultsMatches[index];
|
||||
if (!match || button.disabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const isCheckpoint = match.type === 'checkpoint';
|
||||
const body = isCheckpoint
|
||||
? { recipe_id: match.recipe_id }
|
||||
: { recipe_id: match.recipe_id, lora_index: match.lora_index };
|
||||
const response = await fetch(
|
||||
isCheckpoint
|
||||
? '/api/lm/recipe/checkpoint/restore'
|
||||
: '/api/lm/recipe/lora/restore',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.error || 'Restore failed');
|
||||
}
|
||||
row.classList.add('undone');
|
||||
button.disabled = true;
|
||||
button.textContent = translate('modals.rematchResults.undone', {}, 'Undone');
|
||||
} catch (error) {
|
||||
console.error('Failed to undo rematch match:', error);
|
||||
showToast(
|
||||
'modals.rematchResults.undoFailed',
|
||||
{ message: error.message },
|
||||
'error'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const rematchModalManager = new RematchModalManager();
|
||||
|
||||
Reference in New Issue
Block a user