mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: Implement recipe repair cancellation with UI support and refactor LoadingManager to a singleton.
This commit is contained in:
@@ -167,6 +167,7 @@
|
||||
"label": "Recipe-Daten reparieren",
|
||||
"loading": "Recipe-Daten werden repariert...",
|
||||
"success": "{count} Rezepte erfolgreich repariert.",
|
||||
"cancelled": "Reparatur abgebrochen. {count} Rezepte wurden repariert.",
|
||||
"error": "Recipe-Reparatur fehlgeschlagen: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "Repair recipes data",
|
||||
"loading": "Repairing recipe data...",
|
||||
"success": "Successfully repaired {count} recipes.",
|
||||
"cancelled": "Repair cancelled. {count} recipes were repaired.",
|
||||
"error": "Recipe repair failed: {message}"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "Reparar datos de recetas",
|
||||
"loading": "Reparando datos de recetas...",
|
||||
"success": "Se repararon con éxito {count} recetas.",
|
||||
"cancelled": "Reparación cancelada. {count} recetas fueron reparadas.",
|
||||
"error": "Error al reparar recetas: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "Réparer les données de recettes",
|
||||
"loading": "Réparation des données de recettes...",
|
||||
"success": "{count} recettes réparées avec succès.",
|
||||
"cancelled": "Réparation annulée. {count} recettes ont été réparées.",
|
||||
"error": "Échec de la réparation des recettes : {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "תיקון נתוני מתכונים",
|
||||
"loading": "מתקן נתוני מתכונים...",
|
||||
"success": "תוקנו בהצלחה {count} מתכונים.",
|
||||
"cancelled": "תיקון בוטל. {count} מתכונים תוקנו.",
|
||||
"error": "תיקון המתכונים נכשל: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "レシピデータの修復",
|
||||
"loading": "レシピデータを修復中...",
|
||||
"success": "{count} 件のレシピを正常に修復しました。",
|
||||
"cancelled": "修復がキャンセルされました。{count}個のレシピが修復されました。",
|
||||
"error": "レシピの修復に失敗しました: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "레시피 데이터 복구",
|
||||
"loading": "레시피 데이터 복구 중...",
|
||||
"success": "{count}개의 레시피가 성공적으로 복구되었습니다.",
|
||||
"cancelled": "수리가 취소되었습니다. {count}개의 레시피가 수리되었습니다.",
|
||||
"error": "레시피 복구 실패: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "Восстановить данные рецептов",
|
||||
"loading": "Восстановление данных рецептов...",
|
||||
"success": "Успешно восстановлено {count} рецептов.",
|
||||
"cancelled": "Восстановление отменено. {count} рецептов было восстановлено.",
|
||||
"error": "Ошибка восстановления рецептов: {message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "修复配方数据",
|
||||
"loading": "正在修复配方数据...",
|
||||
"success": "成功修复了 {count} 个配方。",
|
||||
"cancelled": "修复已取消。已修复 {count} 个配方。",
|
||||
"error": "配方修复失败:{message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "浏览器插件教程"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"label": "修復配方資料",
|
||||
"loading": "正在修復配方資料...",
|
||||
"success": "成功修復 {count} 個配方。",
|
||||
"cancelled": "修復已取消。已修復 {count} 個配方。",
|
||||
"error": "配方修復失敗:{message}"
|
||||
}
|
||||
},
|
||||
@@ -1528,4 +1529,4 @@
|
||||
"learnMore": "LM Civitai Extension Tutorial"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ class RecipeHandlerSet:
|
||||
"scan_recipes": self.query.scan_recipes,
|
||||
"move_recipe": self.management.move_recipe,
|
||||
"repair_recipes": self.management.repair_recipes,
|
||||
"cancel_repair": self.management.cancel_repair,
|
||||
"repair_recipe": self.management.repair_recipe,
|
||||
"get_repair_progress": self.management.get_repair_progress,
|
||||
}
|
||||
@@ -530,9 +531,11 @@ class RecipeManagementHandler:
|
||||
return web.json_response({"success": False, "error": "Recipe scanner unavailable"}, status=503)
|
||||
|
||||
# Check if already running
|
||||
if self._ws_manager.get_recipe_repair_progress():
|
||||
if self._ws_manager.is_recipe_repair_running():
|
||||
return web.json_response({"success": False, "error": "Recipe repair already in progress"}, status=409)
|
||||
|
||||
recipe_scanner.reset_cancellation()
|
||||
|
||||
async def progress_callback(data):
|
||||
await self._ws_manager.broadcast_recipe_repair_progress(data)
|
||||
|
||||
@@ -551,6 +554,8 @@ class RecipeManagementHandler:
|
||||
finally:
|
||||
# Keep the final status for a while so the UI can see it
|
||||
await asyncio.sleep(5)
|
||||
# Don't cleanup if it was cancelled, let the UI see the cancelled state for a bit?
|
||||
# Actually cleanup_recipe_repair_progress is fine as long as we waited enough.
|
||||
self._ws_manager.cleanup_recipe_repair_progress()
|
||||
|
||||
asyncio.create_task(run_repair())
|
||||
@@ -560,6 +565,19 @@ class RecipeManagementHandler:
|
||||
self._logger.error("Error starting recipe repair: %s", exc, exc_info=True)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def cancel_repair(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
await self._ensure_dependencies_ready()
|
||||
recipe_scanner = self._recipe_scanner_getter()
|
||||
if recipe_scanner is None:
|
||||
return web.json_response({"success": False, "error": "Recipe scanner unavailable"}, status=503)
|
||||
|
||||
recipe_scanner.cancel_task()
|
||||
return web.json_response({"success": True, "message": "Cancellation requested"})
|
||||
except Exception as exc:
|
||||
self._logger.error("Error cancelling recipe repair: %s", exc, exc_info=True)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def repair_recipe(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
await self._ensure_dependencies_ready()
|
||||
|
||||
@@ -44,6 +44,7 @@ ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
||||
RouteDefinition("GET", "/api/lm/recipes/for-lora", "get_recipes_for_lora"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/scan", "scan_recipes"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/repair", "repair_recipes"),
|
||||
RouteDefinition("POST", "/api/lm/recipes/cancel-repair", "cancel_repair"),
|
||||
RouteDefinition("POST", "/api/lm/recipe/{recipe_id}/repair", "repair_recipe"),
|
||||
RouteDefinition("GET", "/api/lm/recipes/repair-progress", "get_repair_progress"),
|
||||
)
|
||||
|
||||
@@ -73,6 +73,7 @@ class RecipeScanner:
|
||||
self._mutation_lock = asyncio.Lock()
|
||||
self._post_scan_task: Optional[asyncio.Task] = None
|
||||
self._resort_tasks: Set[asyncio.Task] = set()
|
||||
self._cancel_requested = False
|
||||
if lora_scanner:
|
||||
self._lora_scanner = lora_scanner
|
||||
if checkpoint_scanner:
|
||||
@@ -114,6 +115,19 @@ class RecipeScanner:
|
||||
self._civitai_client = await ServiceRegistry.get_civitai_client()
|
||||
return self._civitai_client
|
||||
|
||||
def cancel_task(self) -> None:
|
||||
"""Request cancellation of the current long-running task."""
|
||||
self._cancel_requested = True
|
||||
logger.info("Recipe Scanner: Cancellation requested")
|
||||
|
||||
def reset_cancellation(self) -> None:
|
||||
"""Reset the cancellation flag."""
|
||||
self._cancel_requested = False
|
||||
|
||||
def is_cancelled(self) -> bool:
|
||||
"""Check if cancellation has been requested."""
|
||||
return self._cancel_requested
|
||||
|
||||
async def repair_all_recipes(
|
||||
self,
|
||||
progress_callback: Optional[Callable[[Dict], Any]] = None
|
||||
@@ -127,6 +141,8 @@ class RecipeScanner:
|
||||
Returns:
|
||||
Dict summary of repair results
|
||||
"""
|
||||
if progress_callback:
|
||||
await progress_callback({"status": "started"})
|
||||
async with self._mutation_lock:
|
||||
cache = await self.get_cached_data()
|
||||
all_recipes = list(cache.raw_data)
|
||||
@@ -136,8 +152,29 @@ class RecipeScanner:
|
||||
errors_count = 0
|
||||
|
||||
civitai_client = await self._get_civitai_client()
|
||||
self.reset_cancellation()
|
||||
|
||||
for i, recipe in enumerate(all_recipes):
|
||||
if self.is_cancelled():
|
||||
logger.info("Recipe repair cancelled by user")
|
||||
if progress_callback:
|
||||
await progress_callback({
|
||||
"status": "cancelled",
|
||||
"current": i,
|
||||
"total": total,
|
||||
"repaired": repaired_count,
|
||||
"skipped": skipped_count,
|
||||
"errors": errors_count
|
||||
})
|
||||
return {
|
||||
"success": False,
|
||||
"status": "cancelled",
|
||||
"repaired": repaired_count,
|
||||
"skipped": skipped_count,
|
||||
"errors": errors_count,
|
||||
"total": total
|
||||
}
|
||||
|
||||
try:
|
||||
# Report progress
|
||||
if progress_callback:
|
||||
|
||||
@@ -212,8 +212,16 @@ class WebSocketManager:
|
||||
return self._recipe_repair_progress
|
||||
|
||||
def cleanup_recipe_repair_progress(self):
|
||||
"""Clear recipe repair progress data"""
|
||||
self._recipe_repair_progress = None
|
||||
"""Clear recipe repair progress data if it is in a finished state"""
|
||||
if self._recipe_repair_progress and self._recipe_repair_progress.get('status') in ['completed', 'cancelled', 'error']:
|
||||
self._recipe_repair_progress = None
|
||||
|
||||
def is_recipe_repair_running(self) -> bool:
|
||||
"""Check if recipe repair is currently running"""
|
||||
if not self._recipe_repair_progress:
|
||||
return False
|
||||
status = self._recipe_repair_progress.get('status')
|
||||
return status in ['started', 'processing']
|
||||
|
||||
def is_auto_organize_running(self) -> bool:
|
||||
"""Check if auto-organize is currently running"""
|
||||
|
||||
@@ -279,6 +279,7 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
);
|
||||
|
||||
const progressUI = state.loadingManager?.showEnhancedProgress(loadingMessage);
|
||||
progressUI?.showCancelButton(() => this.cancelRepair());
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/lm/recipes/repair', {
|
||||
@@ -316,6 +317,14 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
}
|
||||
} else if (p.status === 'error') {
|
||||
throw new Error(p.error || 'Repair failed');
|
||||
} else if (p.status === 'cancelled') {
|
||||
isComplete = true;
|
||||
progressUI?.complete(translate(
|
||||
'globalContextMenu.repairRecipes.cancelled',
|
||||
{ count: p.repaired },
|
||||
`Repair cancelled. ${p.repaired} recipes were repaired.`
|
||||
));
|
||||
showToast('globalContextMenu.repairRecipes.cancelled', { count: p.repaired }, 'info');
|
||||
}
|
||||
} else if (progressResponse.status === 404) {
|
||||
// Progress might have finished quickly and been cleaned up
|
||||
@@ -337,4 +346,14 @@ export class GlobalContextMenu extends BaseContextMenu {
|
||||
menuItem?.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
async cancelRepair() {
|
||||
try {
|
||||
await fetch('/api/lm/recipes/cancel-repair', {
|
||||
method: 'POST',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel recipe repair:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import { formatFileSize } from '../utils/formatters.js';
|
||||
// Loading management
|
||||
export class LoadingManager {
|
||||
constructor() {
|
||||
if (LoadingManager.instance) {
|
||||
return LoadingManager.instance;
|
||||
}
|
||||
|
||||
this.overlay = document.getElementById('loading-overlay');
|
||||
|
||||
if (!this.overlay) {
|
||||
@@ -59,6 +63,8 @@ export class LoadingManager {
|
||||
};
|
||||
|
||||
this.detailsContainer = null; // Will be created when needed
|
||||
|
||||
LoadingManager.instance = this;
|
||||
}
|
||||
|
||||
show(message = 'Loading...', progress = 0) {
|
||||
|
||||
Reference in New Issue
Block a user