Refactor card update functions to unify model and Lora card handling; remove unused metadata path update logic. See #228

This commit is contained in:
Will Miao
2025-06-14 09:39:59 +08:00
parent f966514bc7
commit 9e553bb87b
12 changed files with 58 additions and 143 deletions

View File

@@ -374,32 +374,6 @@ class LoraScanner(ModelScanner):
return letters 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 # Lora-specific hash index functionality
def has_lora_hash(self, sha256: str) -> bool: def has_lora_hash(self, sha256: str) -> bool:
"""Check if a LoRA with given hash exists""" """Check if a LoRA with given hash exists"""

View File

@@ -1042,7 +1042,7 @@ class ModelScanner:
metadata['file_path'] = model_path.replace(os.sep, '/') 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_dir = os.path.dirname(model_path)
preview_name = os.path.splitext(os.path.basename(metadata['preview_url']))[0] preview_name = os.path.splitext(os.path.basename(metadata['preview_url']))[0]
preview_ext = os.path.splitext(metadata['preview_url'])[1] preview_ext = os.path.splitext(metadata['preview_url'])[1]

View File

@@ -4,7 +4,6 @@ import { showLoraModal } from './loraModal/index.js';
import { bulkManager } from '../managers/BulkManager.js'; import { bulkManager } from '../managers/BulkManager.js';
import { NSFW_LEVELS } from '../utils/constants.js'; import { NSFW_LEVELS } from '../utils/constants.js';
import { replacePreview, saveModelMetadata } from '../api/loraApi.js' import { replacePreview, saveModelMetadata } from '../api/loraApi.js'
import { showDeleteModal } from '../utils/modalUtils.js';
// Add a global event delegation handler // Add a global event delegation handler
export function setupLoraCardEventDelegation() { export function setupLoraCardEventDelegation() {

View File

@@ -4,7 +4,7 @@
*/ */
import { showToast } from '../../utils/uiHelpers.js'; import { showToast } from '../../utils/uiHelpers.js';
import { BASE_MODELS } from '../../utils/constants.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'; import { saveModelMetadata, renameCheckpointFile } from '../../api/checkpointApi.js';
/** /**
@@ -115,7 +115,7 @@ export function setupModelNameEditing(filePath) {
await saveModelMetadata(filePath, { model_name: newModelName }); await saveModelMetadata(filePath, { model_name: newModelName });
// Update the corresponding checkpoint card's dataset and display // 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 // BUGFIX: Directly update the card's dataset.name attribute to ensure
// it's correctly read when reopening the modal // it's correctly read when reopening the modal
@@ -301,7 +301,7 @@ async function saveBaseModel(filePath, originalValue) {
await saveModelMetadata(filePath, { base_model: newBaseModel }); await saveModelMetadata(filePath, { base_model: newBaseModel });
// Update the card with the new base model // Update the card with the new base model
updateCheckpointCard(filePath, { base_model: newBaseModel }); updateModelCard(filePath, { base_model: newBaseModel });
showToast('Base model updated successfully', 'success'); showToast('Base model updated successfully', 'success');
} catch (error) { } catch (error) {
@@ -431,7 +431,7 @@ export function setupFileNameEditing(filePath) {
const newFilePath = [...pathParts, newFileName].join('/'); const newFilePath = [...pathParts, newFileName].join('/');
// Update the checkpoint card with new file path // Update the checkpoint card with new file path
updateCheckpointCard(filePath, { updateModelCard(filePath, {
filepath: newFilePath, filepath: newFilePath,
file_name: newFileName file_name: newFileName
}); });

View File

@@ -4,7 +4,6 @@
*/ */
import { showToast } from '../../utils/uiHelpers.js'; import { showToast } from '../../utils/uiHelpers.js';
import { saveModelMetadata } from '../../api/checkpointApi.js'; import { saveModelMetadata } from '../../api/checkpointApi.js';
import { updateCheckpointCard } from '../../utils/cardUpdater.js';
// Preset tag suggestions // Preset tag suggestions
const PRESET_TAGS = [ const PRESET_TAGS = [

View File

@@ -15,7 +15,7 @@ import {
import { setupTagEditMode } from './ModelTags.js'; // Add import for tag editing import { setupTagEditMode } from './ModelTags.js'; // Add import for tag editing
import { saveModelMetadata } from '../../api/checkpointApi.js'; import { saveModelMetadata } from '../../api/checkpointApi.js';
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.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'; import { state } from '../../state/index.js';
/** /**
@@ -264,7 +264,7 @@ async function saveNotes(filePath) {
await saveModelMetadata(filePath, { notes: content }); await saveModelMetadata(filePath, { notes: content });
// Update the corresponding checkpoint card's dataset // Update the corresponding checkpoint card's dataset
updateCheckpointCard(filePath, { notes: content }); updateModelCard(filePath, { notes: content });
showToast('Notes saved successfully', 'success'); showToast('Notes saved successfully', 'success');
} catch (error) { } catch (error) {

View File

@@ -4,7 +4,7 @@
*/ */
import { showToast } from '../../utils/uiHelpers.js'; import { showToast } from '../../utils/uiHelpers.js';
import { BASE_MODELS } from '../../utils/constants.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'; import { saveModelMetadata, renameLoraFile } from '../../api/loraApi.js';
/** /**
@@ -116,7 +116,7 @@ export function setupModelNameEditing(filePath) {
await saveModelMetadata(filePath, { model_name: newModelName }); await saveModelMetadata(filePath, { model_name: newModelName });
// Update the corresponding lora card's dataset and display // 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 // BUGFIX: Directly update the card's dataset.name attribute to ensure
// it's correctly read when reopening the modal // it's correctly read when reopening the modal
@@ -305,7 +305,7 @@ async function saveBaseModel(filePath, originalValue) {
await saveModelMetadata(filePath, { base_model: newBaseModel }); await saveModelMetadata(filePath, { base_model: newBaseModel });
// Update the corresponding lora card's dataset // Update the corresponding lora card's dataset
updateLoraCard(filePath, { base_model: newBaseModel }); updateModelCard(filePath, { base_model: newBaseModel });
showToast('Base model updated successfully', 'success'); showToast('Base model updated successfully', 'success');
} catch (error) { } catch (error) {
@@ -434,7 +434,7 @@ export function setupFileNameEditing(filePath) {
// Get the new file path and update the card // Get the new file path and update the card
const newFilePath = filePath.replace(originalValue, newFileName); const newFilePath = filePath.replace(originalValue, newFileName);
// Pass the new file_name in the updates object for proper card update // 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 { } else {
throw new Error(result.error || 'Unknown error'); throw new Error(result.error || 'Unknown error');
} }

View File

@@ -4,7 +4,6 @@
*/ */
import { showToast } from '../../utils/uiHelpers.js'; import { showToast } from '../../utils/uiHelpers.js';
import { saveModelMetadata } from '../../api/loraApi.js'; import { saveModelMetadata } from '../../api/loraApi.js';
import { updateLoraCard } from '../../utils/cardUpdater.js';
// Preset tag suggestions // Preset tag suggestions
const PRESET_TAGS = [ const PRESET_TAGS = [

View File

@@ -18,7 +18,7 @@ import {
} from './ModelMetadata.js'; } from './ModelMetadata.js';
import { saveModelMetadata } from '../../api/loraApi.js'; import { saveModelMetadata } from '../../api/loraApi.js';
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.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'; import { state } from '../../state/index.js';
/** /**
@@ -270,7 +270,7 @@ window.saveNotes = async function(filePath) {
await saveModelMetadata(filePath, { notes: content }); await saveModelMetadata(filePath, { notes: content });
// Update the corresponding lora card's dataset // Update the corresponding lora card's dataset
updateLoraCard(filePath, { notes: content }); updateModelCard(filePath, { notes: content });
showToast('Notes saved successfully', 'success'); showToast('Notes saved successfully', 'success');
} catch (error) { } catch (error) {
@@ -337,7 +337,7 @@ function setupEditableFields(filePath) {
}); });
// Update the card with the new usage tips // Update the card with the new usage tips
updateLoraCard(filePath, { usage_tips: newPresetsJson }); updateModelCard(filePath, { usage_tips: newPresetsJson });
presetTags.innerHTML = renderPresetTags(currentPresets); presetTags.innerHTML = renderPresetTags(currentPresets);

View File

@@ -1,8 +1,8 @@
import { showToast } from '../utils/uiHelpers.js'; import { showToast } from '../utils/uiHelpers.js';
import { state } from '../state/index.js'; import { state, getCurrentPageState } from '../state/index.js';
import { resetAndReload } from '../api/loraApi.js';
import { modalManager } from './ModalManager.js'; import { modalManager } from './ModalManager.js';
import { getStorageItem } from '../utils/storageHelpers.js'; import { getStorageItem } from '../utils/storageHelpers.js';
import { updateModelCard } from '../utils/cardUpdater.js';
class MoveManager { class MoveManager {
constructor() { constructor() {
@@ -136,13 +136,45 @@ class MoveManager {
if (this.bulkFilePaths) { if (this.bulkFilePaths) {
// Bulk move mode // Bulk move mode
await this.moveBulkModels(this.bulkFilePaths, targetPath); 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 { } else {
// Single move mode // Single move mode
await this.moveSingleModel(this.currentFilePath, targetPath); 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'); modalManager.closeModal('moveModal');
await resetAndReload(true);
// If we were in bulk mode, exit it after successful move // If we were in bulk mode, exit it after successful move
if (this.bulkFilePaths && state.bulkMode) { if (this.bulkFilePaths && state.bulkMode) {

View File

@@ -801,11 +801,7 @@ export class VirtualScroller {
if (!filePath || this.disabled || this.items.length === 0) return false; if (!filePath || this.disabled || this.items.length === 0) return false;
// Find the index of the item with the matching file path // Find the index of the item with the matching file path
const index = this.items.findIndex(item => const index = this.items.findIndex(item => item.file_path === filePath);
item.file_path === filePath ||
item.filepath === filePath ||
item.path === filePath
);
if (index === -1) { if (index === -1) {
console.warn(`Item with file path ${filePath} not found in virtual scroller data`); console.warn(`Item with file path ${filePath} not found in virtual scroller data`);

View File

@@ -2,129 +2,45 @@
* Utility functions to update checkpoint cards after modal edits * 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 * Update the Lora card after metadata edits in the modal
* @param {string} filePath - Path to the Lora file * @param {string} filePath - Path to the Lora file
* @param {Object} updates - Object containing the updates (model_name, base_model, notes, usage_tips, etc) * @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 // Find the card with matching filepath
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const modelCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
if (!loraCard) return; if (!modelCard) return;
// If file was renamed, update the filepath first
if (newFilePath) {
loraCard.dataset.filepath = newFilePath;
}
// Update card dataset and visual elements based on the updates object // Update card dataset and visual elements based on the updates object
Object.entries(updates).forEach(([key, value]) => { Object.entries(updates).forEach(([key, value]) => {
// Update dataset // Update dataset
loraCard.dataset[key] = value; modelCard.dataset[key] = value;
// Update visual elements based on the property // Update visual elements based on the property
switch(key) { switch(key) {
case 'model_name': case 'model_name':
// Update the model name in the card title // 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; if (titleElement) titleElement.textContent = value;
// Also update the model name in the footer if it exists // 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; if (modelNameElement) modelNameElement.textContent = value;
break; break;
case 'file_name':
// Update the file_name in the dataset
loraCard.dataset.file_name = value;
break;
case 'base_model': case 'base_model':
// Update the base model label in the card header if it exists // 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) { if (baseModelLabel) {
baseModelLabel.textContent = value; baseModelLabel.textContent = value;
baseModelLabel.title = value; baseModelLabel.title = value;
} }
break; 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
} }
/** /**