mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-10 12:59:24 -03:00
feat(recipe): add reimport UI with context menus, progress display, and i18n
- Single recipe right-click menu: Re-import from Source - Bulk context menu: Re-import Metadata for Selected - Progress overlay with LoadingManager for single and bulk operations - Virtual scroller data lookup (replaces fragile DOM querySelector) - Fix dynamic import path for resetAndReload on recipe pages - Add translation keys for all 9 supported languages
This commit is contained in:
@@ -42,10 +42,14 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
const deleteAllItem = this.menu.querySelector('[data-action="delete-all"]');
|
||||
const downloadMissingLorasItem = this.menu.querySelector('[data-action="download-missing-loras"]');
|
||||
const repairMetadataItem = this.menu.querySelector('[data-action="repair-metadata"]');
|
||||
const reimportMetadataItem = this.menu.querySelector('[data-action="reimport-metadata"]');
|
||||
|
||||
if (repairMetadataItem) {
|
||||
repairMetadataItem.style.display = config.repairMetadata ? 'flex' : 'none';
|
||||
}
|
||||
if (reimportMetadataItem) {
|
||||
reimportMetadataItem.style.display = config.reimportMetadata ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (sendToWorkflowAppendItem) {
|
||||
sendToWorkflowAppendItem.style.display = config.sendToWorkflow ? 'flex' : 'none';
|
||||
@@ -264,6 +268,9 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
case 'repair-metadata':
|
||||
bulkManager.repairSelectedRecipes();
|
||||
break;
|
||||
case 'reimport-metadata':
|
||||
bulkManager.reimportSelectedRecipes();
|
||||
break;
|
||||
case 'set-favorite': {
|
||||
const allFavorited = this.countFavoritedInSelection() === state.selectedModels.size;
|
||||
bulkManager.setBulkFavorites(!allFavorited);
|
||||
|
||||
@@ -97,6 +97,9 @@ export class RecipeContextMenu extends BaseContextMenu {
|
||||
// Repair recipe metadata
|
||||
this.repairRecipe(recipeId);
|
||||
break;
|
||||
case 'reimport':
|
||||
this.reimportRecipe(recipeId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +328,35 @@ export class RecipeContextMenu extends BaseContextMenu {
|
||||
showToast('recipes.contextMenu.repair.failed', { message: error.message }, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async reimportRecipe(recipeId) {
|
||||
if (!recipeId) {
|
||||
showToast('recipes.contextMenu.reimport.missingId', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
state.loadingManager.showSimpleLoading('Re-importing recipe from source...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/lm/recipe/${recipeId}/reimport`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
state.loadingManager.hide();
|
||||
showToast('toast.recipes.reimportSuccess', {}, 'success');
|
||||
const { resetAndReload } = await import('../../api/recipeApi.js');
|
||||
resetAndReload(false, { preserveScroll: true });
|
||||
} else {
|
||||
throw new Error(result.error || 'Re-import failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reimporting recipe:', error);
|
||||
state.loadingManager.hide();
|
||||
showToast('recipes.contextMenu.reimport.failed', { message: error.message }, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mix in shared methods from ModelContextMenuMixin
|
||||
|
||||
@@ -86,7 +86,8 @@ export class BulkManager {
|
||||
skipMetadataRefresh: false,
|
||||
setFavorite: true,
|
||||
unfavorite: true,
|
||||
repairMetadata: true
|
||||
repairMetadata: true,
|
||||
reimportMetadata: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -657,6 +658,87 @@ export class BulkManager {
|
||||
}
|
||||
}
|
||||
|
||||
async reimportSelectedRecipes() {
|
||||
if (state.selectedModels.size === 0) {
|
||||
showToast('toast.recipes.noRecipesSelected', {}, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.currentPageType !== 'recipes') {
|
||||
showToast('This operation is only available for recipes', {}, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const filePaths = Array.from(state.selectedModels);
|
||||
const total = filePaths.length;
|
||||
let completed = 0;
|
||||
let failed = 0;
|
||||
|
||||
const recipeMap = new Map();
|
||||
if (state.virtualScroller?.items) {
|
||||
for (const item of state.virtualScroller.items) {
|
||||
if (item.file_path && item.id) {
|
||||
recipeMap.set(item.file_path, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const progressUI = state.loadingManager.showEnhancedProgress(
|
||||
`Re-importing recipe 1/${total}...`
|
||||
);
|
||||
|
||||
try {
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
const filePath = filePaths[i];
|
||||
const recipeItem = recipeMap.get(filePath);
|
||||
const recipeId = recipeItem?.id;
|
||||
const recipeName = recipeItem?.title || recipeId || 'Unknown';
|
||||
|
||||
progressUI.updateProgress(
|
||||
Math.floor((i / total) * 100),
|
||||
recipeName,
|
||||
`Re-importing recipe ${Math.min(i + 1, total)}/${total}...`
|
||||
);
|
||||
|
||||
if (!recipeId) {
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/lm/recipe/${recipeId}/reimport`,
|
||||
{ method: 'POST' }
|
||||
);
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
completed++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
} catch {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
if (completed > 0) {
|
||||
await progressUI.complete(
|
||||
`Re-import complete: ${completed} re-imported, ${failed} failed`
|
||||
);
|
||||
} else {
|
||||
state.loadingManager.hide();
|
||||
showToast('toast.recipes.reimportBulkFailed', {}, 'error');
|
||||
}
|
||||
|
||||
const { resetAndReload: recipeResetAndReload } = await import('../api/recipeApi.js');
|
||||
recipeResetAndReload(false, { preserveScroll: true });
|
||||
this.clearSelection();
|
||||
} catch (error) {
|
||||
console.error('[reimportSelectedRecipes] outer catch:', error);
|
||||
state.loadingManager.hide();
|
||||
showToast('toast.recipes.reimportBulkFailed', {}, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async repairSelectedRecipes() {
|
||||
if (state.selectedModels.size === 0) {
|
||||
showToast('toast.recipes.noRecipesSelected', {}, 'warning');
|
||||
|
||||
Reference in New Issue
Block a user