diff --git a/py/recipes/base.py b/py/recipes/base.py index 26fd8479..f47ba3f0 100644 --- a/py/recipes/base.py +++ b/py/recipes/base.py @@ -79,6 +79,9 @@ class RecipeMetadataParser(ABC): if 'model' in civitai_info and 'name' in civitai_info['model']: lora_entry['name'] = civitai_info['model']['name'] + lora_entry['id'] = civitai_info.get('id') + lora_entry['modelId'] = civitai_info.get('modelId') + # Update version if available if 'name' in civitai_info: lora_entry['version'] = civitai_info.get('name', '') diff --git a/py/recipes/parsers/automatic.py b/py/recipes/parsers/automatic.py index 2f610c08..0632c93d 100644 --- a/py/recipes/parsers/automatic.py +++ b/py/recipes/parsers/automatic.py @@ -184,8 +184,8 @@ class AutomaticMetadataParser(RecipeMetadataParser): if resource.get("type") in ["lora", "lycoris", "hypernet"] and resource.get("modelVersionId"): # Initialize lora entry lora_entry = { - 'id': str(resource.get("modelVersionId")), - 'modelId': str(resource.get("modelId")) if resource.get("modelId") else None, + 'id': resource.get("modelVersionId", 0), + 'modelId': resource.get("modelId", 0), 'name': resource.get("modelName", "Unknown LoRA"), 'version': resource.get("modelVersionName", ""), 'type': resource.get("type", "lora"), diff --git a/py/recipes/parsers/civitai_image.py b/py/recipes/parsers/civitai_image.py index 824abd3e..e43afc48 100644 --- a/py/recipes/parsers/civitai_image.py +++ b/py/recipes/parsers/civitai_image.py @@ -141,8 +141,8 @@ class CivitaiApiMetadataParser(RecipeMetadataParser): if resource.get("type") in ["lora", "lycoris"] or "type" not in resource: # Initialize lora entry with the same structure as in automatic.py lora_entry = { - 'id': str(resource.get("modelVersionId")), - 'modelId': str(resource.get("modelId")) if resource.get("modelId") else None, + 'id': resource.get("modelVersionId", 0), + 'modelId': resource.get("modelId", 0), 'name': resource.get("modelName", "Unknown LoRA"), 'version': resource.get("modelVersionName", ""), 'type': resource.get("type", "lora"), diff --git a/py/recipes/parsers/recipe_format.py b/py/recipes/parsers/recipe_format.py index 1e46ff51..37b71712 100644 --- a/py/recipes/parsers/recipe_format.py +++ b/py/recipes/parsers/recipe_format.py @@ -43,7 +43,7 @@ class RecipeFormatParser(RecipeMetadataParser): for lora in recipe_metadata.get('loras', []): # Convert recipe lora format to frontend format lora_entry = { - 'id': lora.get('modelVersionId', ''), + 'id': int(lora.get('modelVersionId', 0)), 'name': lora.get('modelName', ''), 'version': lora.get('modelVersionName', ''), 'type': 'lora', diff --git a/py/routes/recipe_routes.py b/py/routes/recipe_routes.py index 85cd86ed..f05a1292 100644 --- a/py/routes/recipe_routes.py +++ b/py/routes/recipe_routes.py @@ -648,7 +648,7 @@ class RecipeRoutes: "file_name": lora.get("file_name", "") or os.path.splitext(os.path.basename(lora.get("localPath", "")))[0] if lora.get("localPath") else "", "hash": lora.get("hash", "").lower() if lora.get("hash") else "", "strength": float(lora.get("weight", 1.0)), - "modelVersionId": lora.get("id", ""), + "modelVersionId": lora.get("id", 0), "modelName": lora.get("name", ""), "modelVersionName": lora.get("version", ""), "isDeleted": lora.get("isDeleted", False), # Preserve deletion status in saved recipe @@ -1107,7 +1107,7 @@ class RecipeRoutes: "file_name": lora_name, "hash": lora_info.get("sha256", "").lower() if lora_info else "", "strength": float(lora_strength), - "modelVersionId": lora_info.get("civitai", {}).get("id", "") if lora_info else "", + "modelVersionId": lora_info.get("civitai", {}).get("id", 0) if lora_info else 0, "modelName": lora_info.get("civitai", {}).get("model", {}).get("name", "") if lora_info else lora_name, "modelVersionName": lora_info.get("civitai", {}).get("name", "") if lora_info else "", "isDeleted": False @@ -1296,7 +1296,7 @@ class RecipeRoutes: data = await request.json() # Validate required fields - required_fields = ['recipe_id', 'lora_data', 'target_name'] + required_fields = ['recipe_id', 'lora_index', 'target_name'] for field in required_fields: if field not in data: return web.json_response({ @@ -1304,7 +1304,7 @@ class RecipeRoutes: }, status=400) recipe_id = data['recipe_id'] - lora_data = data['lora_data'] + lora_index = int(data['lora_index']) target_name = data['target_name'] # Get recipe scanner @@ -1324,46 +1324,27 @@ class RecipeRoutes: # Load recipe data with open(recipe_path, 'r', encoding='utf-8') as f: recipe_data = json.load(f) - - # Find the deleted LoRA in the recipe - found = False - updated_lora = None + + lora = recipe_data.get("loras", [])[lora_index] if lora_index < len(recipe_data.get('loras', [])) else None + + if lora is None: + return web.json_response({"error": "LoRA index out of range in recipe"}, status=404) + + # Update LoRA data + lora['isDeleted'] = False + lora['exclude'] = False + lora['file_name'] = target_name - # Identification can be by hash, modelVersionId, or modelName - for i, lora in enumerate(recipe_data.get('loras', [])): - match_found = False - - # Try to match by available identifiers - if 'hash' in lora and 'hash' in lora_data and lora['hash'] == lora_data['hash']: - match_found = True - elif 'modelVersionId' in lora and 'modelVersionId' in lora_data and lora['modelVersionId'] == lora_data['modelVersionId']: - match_found = True - elif 'modelName' in lora and 'modelName' in lora_data and lora['modelName'] == lora_data['modelName']: - match_found = True - - if match_found: - # Update LoRA data - lora['isDeleted'] = False - lora['file_name'] = target_name - - # Update with information from the target LoRA - if 'sha256' in target_lora: - lora['hash'] = target_lora['sha256'].lower() - if target_lora.get("civitai"): - lora['modelName'] = target_lora['civitai']['model']['name'] - lora['modelVersionName'] = target_lora['civitai']['name'] - lora['modelVersionId'] = target_lora['civitai']['id'] - - # Keep original fields for identification - - # Mark as found and store updated lora - found = True - updated_lora = dict(lora) # Make a copy for response - break - - if not found: - return web.json_response({"error": "Could not find matching deleted LoRA in recipe"}, status=404) + # Update with information from the target LoRA + if 'sha256' in target_lora: + lora['hash'] = target_lora['sha256'].lower() + if target_lora.get("civitai"): + lora['modelName'] = target_lora['civitai']['model']['name'] + lora['modelVersionName'] = target_lora['civitai']['name'] + lora['modelVersionId'] = target_lora['civitai']['id'] + updated_lora = dict(lora) # Make a copy for response + # Recalculate recipe fingerprint after updating LoRA from ..utils.utils import calculate_recipe_fingerprint recipe_data['fingerprint'] = calculate_recipe_fingerprint(recipe_data.get('loras', [])) diff --git a/py/utils/exif_utils.py b/py/utils/exif_utils.py index 62e8e7f3..1c5a9f80 100644 --- a/py/utils/exif_utils.py +++ b/py/utils/exif_utils.py @@ -147,7 +147,7 @@ class ExifUtils: "file_name": lora.get("file_name", ""), "hash": lora.get("hash", "").lower() if lora.get("hash") else "", "strength": float(lora.get("strength", 1.0)), - "modelVersionId": lora.get("modelVersionId", ""), + "modelVersionId": lora.get("modelVersionId", 0), "modelName": lora.get("modelName", ""), "modelVersionName": lora.get("modelVersionName", ""), } diff --git a/py/utils/utils.py b/py/utils/utils.py index 054d0482..994d8c54 100644 --- a/py/utils/utils.py +++ b/py/utils/utils.py @@ -142,7 +142,7 @@ def calculate_recipe_fingerprint(loras): # Get the hash - use modelVersionId as fallback if hash is empty hash_value = lora.get("hash", "").lower() if not hash_value and lora.get("isDeleted", False) and lora.get("modelVersionId"): - hash_value = lora.get("modelVersionId") + hash_value = str(lora.get("modelVersionId")) # Skip entries without a valid hash if not hash_value: diff --git a/static/js/components/RecipeCard.js b/static/js/components/RecipeCard.js index 8520ce81..5b838031 100644 --- a/static/js/components/RecipeCard.js +++ b/static/js/components/RecipeCard.js @@ -2,6 +2,7 @@ import { showToast, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js'; import { modalManager } from '../managers/ModalManager.js'; import { getCurrentPageState } from '../state/index.js'; +import { state } from '../state/index.js'; class RecipeCard { constructor(recipe, clickHandler) { @@ -141,6 +142,7 @@ class RecipeCard { try { // Get recipe ID const recipeId = this.recipe.id; + const filePath = this.recipe.file_path; if (!recipeId) { showToast('Cannot delete recipe: Missing recipe ID', 'error'); return; @@ -184,6 +186,7 @@ class RecipeCard { // Store recipe ID in the modal for the delete confirmation handler deleteModal.dataset.recipeId = recipeId; + deleteModal.dataset.filePath = filePath; // Update button event handlers cancelBtn.onclick = () => modalManager.closeModal('deleteModal'); @@ -227,7 +230,7 @@ class RecipeCard { .then(data => { showToast('Recipe deleted successfully', 'success'); - window.recipeManager.loadRecipes(); + state.virtualScroller.removeItemByFilePath(deleteModal.dataset.filePath); modalManager.closeModal('deleteModal'); }) diff --git a/static/js/components/RecipeModal.js b/static/js/components/RecipeModal.js index ce5070a0..e43dbd95 100644 --- a/static/js/components/RecipeModal.js +++ b/static/js/components/RecipeModal.js @@ -950,13 +950,6 @@ class RecipeModal { // Remove .safetensors extension if present fileName = fileName.replace(/\.safetensors$/, ''); - // Get the deleted lora data - const deletedLora = this.currentRecipe.loras[loraIndex]; - if (!deletedLora) { - showToast('Error: Could not find the LoRA in the recipe', 'error'); - return; - } - state.loadingManager.showSimpleLoading('Reconnecting LoRA...'); // Call API to reconnect the LoRA @@ -967,7 +960,7 @@ class RecipeModal { }, body: JSON.stringify({ recipe_id: this.recipeId, - lora_data: deletedLora, + lora_index: loraIndex, target_name: fileName }) });