From 9e553bb87bf46178b4742aedad3b64005600da43 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 14 Jun 2025 09:39:59 +0800 Subject: [PATCH] Refactor card update functions to unify model and Lora card handling; remove unused metadata path update logic. See #228 --- py/services/lora_scanner.py | 26 ----- py/services/model_scanner.py | 2 +- static/js/components/LoraCard.js | 1 - .../checkpointModal/ModelMetadata.js | 8 +- .../components/checkpointModal/ModelTags.js | 1 - static/js/components/checkpointModal/index.js | 4 +- .../js/components/loraModal/ModelMetadata.js | 8 +- static/js/components/loraModal/ModelTags.js | 1 - static/js/components/loraModal/index.js | 6 +- static/js/managers/MoveManager.js | 38 ++++++- static/js/utils/VirtualScroller.js | 6 +- static/js/utils/cardUpdater.js | 100 ++---------------- 12 files changed, 58 insertions(+), 143 deletions(-) diff --git a/py/services/lora_scanner.py b/py/services/lora_scanner.py index c911d184..2e18674d 100644 --- a/py/services/lora_scanner.py +++ b/py/services/lora_scanner.py @@ -374,32 +374,6 @@ class LoraScanner(ModelScanner): return letters - async def _update_metadata_paths(self, metadata_path: str, lora_path: str) -> Dict: - """Update file paths in metadata file""" - try: - with open(metadata_path, 'r', encoding='utf-8') as f: - metadata = json.load(f) - - # Update file_path - metadata['file_path'] = lora_path.replace(os.sep, '/') - - # Update preview_url if exists - if 'preview_url' in metadata: - preview_dir = os.path.dirname(lora_path) - preview_name = os.path.splitext(os.path.basename(metadata['preview_url']))[0] - preview_ext = os.path.splitext(metadata['preview_url'])[1] - new_preview_path = os.path.join(preview_dir, f"{preview_name}{preview_ext}") - metadata['preview_url'] = new_preview_path.replace(os.sep, '/') - - # Save updated metadata - with open(metadata_path, 'w', encoding='utf-8') as f: - json.dump(metadata, f, indent=2, ensure_ascii=False) - - return metadata - - except Exception as e: - logger.error(f"Error updating metadata paths: {e}", exc_info=True) - # Lora-specific hash index functionality def has_lora_hash(self, sha256: str) -> bool: """Check if a LoRA with given hash exists""" diff --git a/py/services/model_scanner.py b/py/services/model_scanner.py index 7d1ba8ef..ea78b32e 100644 --- a/py/services/model_scanner.py +++ b/py/services/model_scanner.py @@ -1042,7 +1042,7 @@ class ModelScanner: metadata['file_path'] = model_path.replace(os.sep, '/') - if 'preview_url' in metadata: + if 'preview_url' in metadata and metadata['preview_url']: preview_dir = os.path.dirname(model_path) preview_name = os.path.splitext(os.path.basename(metadata['preview_url']))[0] preview_ext = os.path.splitext(metadata['preview_url'])[1] diff --git a/static/js/components/LoraCard.js b/static/js/components/LoraCard.js index ab01ee8e..3b2b8475 100644 --- a/static/js/components/LoraCard.js +++ b/static/js/components/LoraCard.js @@ -4,7 +4,6 @@ import { showLoraModal } from './loraModal/index.js'; import { bulkManager } from '../managers/BulkManager.js'; import { NSFW_LEVELS } from '../utils/constants.js'; import { replacePreview, saveModelMetadata } from '../api/loraApi.js' -import { showDeleteModal } from '../utils/modalUtils.js'; // Add a global event delegation handler export function setupLoraCardEventDelegation() { diff --git a/static/js/components/checkpointModal/ModelMetadata.js b/static/js/components/checkpointModal/ModelMetadata.js index 68d3c85f..6bda56d7 100644 --- a/static/js/components/checkpointModal/ModelMetadata.js +++ b/static/js/components/checkpointModal/ModelMetadata.js @@ -4,7 +4,7 @@ */ import { showToast } from '../../utils/uiHelpers.js'; import { BASE_MODELS } from '../../utils/constants.js'; -import { updateCheckpointCard } from '../../utils/cardUpdater.js'; +import { updateModelCard } from '../../utils/cardUpdater.js'; import { saveModelMetadata, renameCheckpointFile } from '../../api/checkpointApi.js'; /** @@ -115,7 +115,7 @@ export function setupModelNameEditing(filePath) { await saveModelMetadata(filePath, { model_name: newModelName }); // Update the corresponding checkpoint card's dataset and display - updateCheckpointCard(filePath, { model_name: newModelName }); + updateModelCard(filePath, { model_name: newModelName }); // BUGFIX: Directly update the card's dataset.name attribute to ensure // it's correctly read when reopening the modal @@ -301,7 +301,7 @@ async function saveBaseModel(filePath, originalValue) { await saveModelMetadata(filePath, { base_model: newBaseModel }); // Update the card with the new base model - updateCheckpointCard(filePath, { base_model: newBaseModel }); + updateModelCard(filePath, { base_model: newBaseModel }); showToast('Base model updated successfully', 'success'); } catch (error) { @@ -431,7 +431,7 @@ export function setupFileNameEditing(filePath) { const newFilePath = [...pathParts, newFileName].join('/'); // Update the checkpoint card with new file path - updateCheckpointCard(filePath, { + updateModelCard(filePath, { filepath: newFilePath, file_name: newFileName }); diff --git a/static/js/components/checkpointModal/ModelTags.js b/static/js/components/checkpointModal/ModelTags.js index bde0b0b6..b5940f3c 100644 --- a/static/js/components/checkpointModal/ModelTags.js +++ b/static/js/components/checkpointModal/ModelTags.js @@ -4,7 +4,6 @@ */ import { showToast } from '../../utils/uiHelpers.js'; import { saveModelMetadata } from '../../api/checkpointApi.js'; -import { updateCheckpointCard } from '../../utils/cardUpdater.js'; // Preset tag suggestions const PRESET_TAGS = [ diff --git a/static/js/components/checkpointModal/index.js b/static/js/components/checkpointModal/index.js index c190e6f6..bcba5b0d 100644 --- a/static/js/components/checkpointModal/index.js +++ b/static/js/components/checkpointModal/index.js @@ -15,7 +15,7 @@ import { import { setupTagEditMode } from './ModelTags.js'; // Add import for tag editing import { saveModelMetadata } from '../../api/checkpointApi.js'; import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js'; -import { updateCheckpointCard } from '../../utils/cardUpdater.js'; +import { updateModelCard } from '../../utils/cardUpdater.js'; import { state } from '../../state/index.js'; /** @@ -264,7 +264,7 @@ async function saveNotes(filePath) { await saveModelMetadata(filePath, { notes: content }); // Update the corresponding checkpoint card's dataset - updateCheckpointCard(filePath, { notes: content }); + updateModelCard(filePath, { notes: content }); showToast('Notes saved successfully', 'success'); } catch (error) { diff --git a/static/js/components/loraModal/ModelMetadata.js b/static/js/components/loraModal/ModelMetadata.js index d49d9cca..5ae63d31 100644 --- a/static/js/components/loraModal/ModelMetadata.js +++ b/static/js/components/loraModal/ModelMetadata.js @@ -4,7 +4,7 @@ */ import { showToast } from '../../utils/uiHelpers.js'; import { BASE_MODELS } from '../../utils/constants.js'; -import { updateLoraCard } from '../../utils/cardUpdater.js'; +import { updateModelCard } from '../../utils/cardUpdater.js'; import { saveModelMetadata, renameLoraFile } from '../../api/loraApi.js'; /** @@ -116,7 +116,7 @@ export function setupModelNameEditing(filePath) { await saveModelMetadata(filePath, { model_name: newModelName }); // Update the corresponding lora card's dataset and display - updateLoraCard(filePath, { model_name: newModelName }); + updateModelCard(filePath, { model_name: newModelName }); // BUGFIX: Directly update the card's dataset.name attribute to ensure // it's correctly read when reopening the modal @@ -305,7 +305,7 @@ async function saveBaseModel(filePath, originalValue) { await saveModelMetadata(filePath, { base_model: newBaseModel }); // Update the corresponding lora card's dataset - updateLoraCard(filePath, { base_model: newBaseModel }); + updateModelCard(filePath, { base_model: newBaseModel }); showToast('Base model updated successfully', 'success'); } catch (error) { @@ -434,7 +434,7 @@ export function setupFileNameEditing(filePath) { // Get the new file path and update the card const newFilePath = filePath.replace(originalValue, newFileName); // Pass the new file_name in the updates object for proper card update - updateLoraCard(filePath, { file_name: newFileName }, newFilePath); + updateModelCard(filePath, { file_name: newFileName, filepath: newFilePath }); } else { throw new Error(result.error || 'Unknown error'); } diff --git a/static/js/components/loraModal/ModelTags.js b/static/js/components/loraModal/ModelTags.js index 43c5fefb..5d31006b 100644 --- a/static/js/components/loraModal/ModelTags.js +++ b/static/js/components/loraModal/ModelTags.js @@ -4,7 +4,6 @@ */ import { showToast } from '../../utils/uiHelpers.js'; import { saveModelMetadata } from '../../api/loraApi.js'; -import { updateLoraCard } from '../../utils/cardUpdater.js'; // Preset tag suggestions const PRESET_TAGS = [ diff --git a/static/js/components/loraModal/index.js b/static/js/components/loraModal/index.js index a2f4db1d..49b498d0 100644 --- a/static/js/components/loraModal/index.js +++ b/static/js/components/loraModal/index.js @@ -18,7 +18,7 @@ import { } from './ModelMetadata.js'; import { saveModelMetadata } from '../../api/loraApi.js'; import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js'; -import { updateLoraCard } from '../../utils/cardUpdater.js'; +import { updateModelCard } from '../../utils/cardUpdater.js'; import { state } from '../../state/index.js'; /** @@ -270,7 +270,7 @@ window.saveNotes = async function(filePath) { await saveModelMetadata(filePath, { notes: content }); // Update the corresponding lora card's dataset - updateLoraCard(filePath, { notes: content }); + updateModelCard(filePath, { notes: content }); showToast('Notes saved successfully', 'success'); } catch (error) { @@ -337,7 +337,7 @@ function setupEditableFields(filePath) { }); // Update the card with the new usage tips - updateLoraCard(filePath, { usage_tips: newPresetsJson }); + updateModelCard(filePath, { usage_tips: newPresetsJson }); presetTags.innerHTML = renderPresetTags(currentPresets); diff --git a/static/js/managers/MoveManager.js b/static/js/managers/MoveManager.js index c2fad001..4e9cdec9 100644 --- a/static/js/managers/MoveManager.js +++ b/static/js/managers/MoveManager.js @@ -1,8 +1,8 @@ import { showToast } from '../utils/uiHelpers.js'; -import { state } from '../state/index.js'; -import { resetAndReload } from '../api/loraApi.js'; +import { state, getCurrentPageState } from '../state/index.js'; import { modalManager } from './ModalManager.js'; import { getStorageItem } from '../utils/storageHelpers.js'; +import { updateModelCard } from '../utils/cardUpdater.js'; class MoveManager { constructor() { @@ -136,13 +136,45 @@ class MoveManager { if (this.bulkFilePaths) { // Bulk move mode await this.moveBulkModels(this.bulkFilePaths, targetPath); + + // Update virtual scroller if in active folder view + const pageState = getCurrentPageState(); + if (pageState.activeFolder !== null && state.virtualScroller) { + // Remove moved items from virtual scroller instead of reloading + this.bulkFilePaths.forEach(filePath => { + state.virtualScroller.removeItemByFilePath(filePath); + }); + } else { + // Update the model cards' filepath in the DOM + this.bulkFilePaths.forEach(filePath => { + // Extract filename from original path + const filename = filePath.substring(filePath.lastIndexOf('/') + 1); + // Construct new filepath + const newFilePath = `${targetPath}/${filename}`; + // Update the card with new filepath + updateModelCard(filePath, {filepath: newFilePath}); + }); + } } else { // Single move mode await this.moveSingleModel(this.currentFilePath, targetPath); + + // Update virtual scroller if in active folder view + const pageState = getCurrentPageState(); + if (pageState.activeFolder !== null && state.virtualScroller) { + // Remove moved item from virtual scroller instead of reloading + state.virtualScroller.removeItemByFilePath(this.currentFilePath); + } else { + // Extract filename from original path + const filename = this.currentFilePath.substring(this.currentFilePath.lastIndexOf('/') + 1); + // Construct new filepath + const newFilePath = `${targetPath}/${filename}`; + // Update the card with new filepath + updateModelCard(this.currentFilePath, {filepath: newFilePath}); + } } modalManager.closeModal('moveModal'); - await resetAndReload(true); // If we were in bulk mode, exit it after successful move if (this.bulkFilePaths && state.bulkMode) { diff --git a/static/js/utils/VirtualScroller.js b/static/js/utils/VirtualScroller.js index 1e307594..9b2bb365 100644 --- a/static/js/utils/VirtualScroller.js +++ b/static/js/utils/VirtualScroller.js @@ -801,11 +801,7 @@ export class VirtualScroller { if (!filePath || this.disabled || this.items.length === 0) return false; // Find the index of the item with the matching file path - const index = this.items.findIndex(item => - item.file_path === filePath || - item.filepath === filePath || - item.path === filePath - ); + const index = this.items.findIndex(item => item.file_path === filePath); if (index === -1) { console.warn(`Item with file path ${filePath} not found in virtual scroller data`); diff --git a/static/js/utils/cardUpdater.js b/static/js/utils/cardUpdater.js index 06c08735..3a0263d5 100644 --- a/static/js/utils/cardUpdater.js +++ b/static/js/utils/cardUpdater.js @@ -2,129 +2,45 @@ * Utility functions to update checkpoint cards after modal edits */ -/** - * Update the checkpoint card after metadata edits in the modal - * @param {string} filePath - Path to the checkpoint file - * @param {Object} updates - Object containing the updates (model_name, base_model, etc) - */ -export function updateCheckpointCard(filePath, updates) { - // Find the card with matching filepath - const checkpointCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); - if (!checkpointCard) return; - - // Update card dataset and visual elements based on the updates object - Object.entries(updates).forEach(([key, value]) => { - // Update dataset - checkpointCard.dataset[key] = value; - - // Update visual elements based on the property - switch(key) { - case 'name': // model_name - // Update the model name in the footer - const modelNameElement = checkpointCard.querySelector('.model-name'); - if (modelNameElement) modelNameElement.textContent = value; - break; - - case 'base_model': - // Update the base model label in the card header - const baseModelLabel = checkpointCard.querySelector('.base-model-label'); - if (baseModelLabel) { - baseModelLabel.textContent = value; - baseModelLabel.title = value; - } - break; - - case 'filepath': - // The filepath was changed (file renamed), update the dataset - checkpointCard.dataset.filepath = value; - break; - - case 'tags': - // Update tags if they're displayed on the card - try { - checkpointCard.dataset.tags = JSON.stringify(value); - } catch (e) { - console.error('Failed to update tags:', e); - } - break; - - // Add other properties as needed - } - }); -} - /** * Update the Lora card after metadata edits in the modal * @param {string} filePath - Path to the Lora file * @param {Object} updates - Object containing the updates (model_name, base_model, notes, usage_tips, etc) - * @param {string} [newFilePath] - Optional new file path if the file has been renamed */ -export function updateLoraCard(filePath, updates, newFilePath) { +export function updateModelCard(filePath, updates) { // Find the card with matching filepath - const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); - if (!loraCard) return; - - // If file was renamed, update the filepath first - if (newFilePath) { - loraCard.dataset.filepath = newFilePath; - } + const modelCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); + if (!modelCard) return; // Update card dataset and visual elements based on the updates object Object.entries(updates).forEach(([key, value]) => { // Update dataset - loraCard.dataset[key] = value; + modelCard.dataset[key] = value; // Update visual elements based on the property switch(key) { case 'model_name': // Update the model name in the card title - const titleElement = loraCard.querySelector('.card-title'); + const titleElement = modelCard.querySelector('.card-title'); if (titleElement) titleElement.textContent = value; // Also update the model name in the footer if it exists - const modelNameElement = loraCard.querySelector('.model-name'); + const modelNameElement = modelCard.querySelector('.model-name'); if (modelNameElement) modelNameElement.textContent = value; break; - - case 'file_name': - // Update the file_name in the dataset - loraCard.dataset.file_name = value; - break; case 'base_model': // Update the base model label in the card header if it exists - const baseModelLabel = loraCard.querySelector('.base-model-label'); + const baseModelLabel = modelCard.querySelector('.base-model-label'); if (baseModelLabel) { baseModelLabel.textContent = value; baseModelLabel.title = value; } break; - - case 'tags': - // Update tags if they're displayed on the card - try { - if (typeof value === 'string') { - loraCard.dataset.tags = value; - } else { - loraCard.dataset.tags = JSON.stringify(value); - } - - // If there's a tag container, update its content - const tagContainer = loraCard.querySelector('.card-tags'); - if (tagContainer) { - // This depends on how your tags are rendered - // You may need to update this logic based on your tag rendering function - } - } catch (e) { - console.error('Failed to update tags:', e); - } - break; - - // No visual updates needed for notes, usage_tips as they're typically not shown on cards } }); - return loraCard; // Return the updated card element for chaining + return modelCard; // Return the updated card element for chaining } /**