feat: Add save metadata route and update checkpoint card functionality

This commit is contained in:
Will Miao
2025-04-12 11:18:21 +08:00
parent 10a4fe04d1
commit e84a8a72c5
7 changed files with 250 additions and 120 deletions

View File

@@ -51,6 +51,7 @@ class CheckpointsRoutes:
app.router.add_post('/api/checkpoints/fetch-civitai', self.fetch_civitai) app.router.add_post('/api/checkpoints/fetch-civitai', self.fetch_civitai)
app.router.add_post('/api/checkpoints/replace-preview', self.replace_preview) app.router.add_post('/api/checkpoints/replace-preview', self.replace_preview)
app.router.add_post('/api/checkpoints/download', self.download_checkpoint) app.router.add_post('/api/checkpoints/download', self.download_checkpoint)
app.router.add_post('/api/checkpoints/save-metadata', self.save_metadata) # Add new route
async def get_checkpoints(self, request): async def get_checkpoints(self, request):
"""Get paginated checkpoint data""" """Get paginated checkpoint data"""
@@ -522,3 +523,44 @@ class CheckpointsRoutes:
"success": False, "success": False,
"error": str(e) "error": str(e)
}, status=500) }, status=500)
async def save_metadata(self, request: web.Request) -> web.Response:
"""Handle saving metadata updates for checkpoints"""
try:
if self.scanner is None:
self.scanner = await ServiceRegistry.get_checkpoint_scanner()
data = await request.json()
file_path = data.get('file_path')
if not file_path:
return web.Response(text='File path is required', status=400)
# Remove file path from data to avoid saving it
metadata_updates = {k: v for k, v in data.items() if k != 'file_path'}
# Get metadata file path
metadata_path = os.path.splitext(file_path)[0] + '.metadata.json'
# Load existing metadata
metadata = await ModelRouteUtils.load_local_metadata(metadata_path)
# Update metadata
metadata.update(metadata_updates)
# Save updated metadata
with open(metadata_path, 'w', encoding='utf-8') as f:
json.dump(metadata, f, indent=2, ensure_ascii=False)
# Update cache
await self.scanner.update_single_model_cache(file_path, file_path, metadata)
# If model_name was updated, resort the cache
if 'model_name' in metadata_updates:
cache = await self.scanner.get_cached_data()
await cache.resort(name_only=True)
return web.json_response({'success': True})
except Exception as e:
logger.error(f"Error saving checkpoint metadata: {e}", exc_info=True)
return web.Response(text=str(e), status=500)

View File

@@ -15,6 +15,7 @@ export function createCheckpointCard(checkpoint) {
card.dataset.modified = checkpoint.modified; card.dataset.modified = checkpoint.modified;
card.dataset.file_size = checkpoint.file_size; card.dataset.file_size = checkpoint.file_size;
card.dataset.from_civitai = checkpoint.from_civitai; card.dataset.from_civitai = checkpoint.from_civitai;
card.dataset.notes = checkpoint.notes || '';
card.dataset.base_model = checkpoint.base_model || 'Unknown'; card.dataset.base_model = checkpoint.base_model || 'Unknown';
// Store metadata if available // Store metadata if available
@@ -124,6 +125,7 @@ export function createCheckpointCard(checkpoint) {
file_size: parseInt(card.dataset.file_size || '0'), file_size: parseInt(card.dataset.file_size || '0'),
from_civitai: card.dataset.from_civitai === 'true', from_civitai: card.dataset.from_civitai === 'true',
base_model: card.dataset.base_model, base_model: card.dataset.base_model,
notes: card.dataset.notes || '',
preview_url: versionedPreviewUrl, preview_url: versionedPreviewUrl,
// Parse civitai metadata from the card's dataset // Parse civitai metadata from the card's dataset
civitai: (() => { civitai: (() => {

View File

@@ -4,6 +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';
/** /**
* Save model metadata to the server * Save model metadata to the server
@@ -12,7 +13,7 @@ import { BASE_MODELS } from '../../utils/constants.js';
* @returns {Promise} - Promise that resolves with the server response * @returns {Promise} - Promise that resolves with the server response
*/ */
export async function saveModelMetadata(filePath, data) { export async function saveModelMetadata(filePath, data) {
const response = await fetch('/checkpoints/api/save-metadata', { const response = await fetch('/api/checkpoints/save-metadata', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -32,8 +33,9 @@ export async function saveModelMetadata(filePath, data) {
/** /**
* Set up model name editing functionality * Set up model name editing functionality
* @param {string} filePath - The full file path of the model.
*/ */
export function setupModelNameEditing() { export function setupModelNameEditing(filePath) {
const modelNameContent = document.querySelector('.model-name-content'); const modelNameContent = document.querySelector('.model-name-content');
const editBtn = document.querySelector('.edit-model-name-btn'); const editBtn = document.querySelector('.edit-model-name-btn');
@@ -76,10 +78,7 @@ export function setupModelNameEditing() {
if (this.textContent.trim() === '') { if (this.textContent.trim() === '') {
// Restore original model name if empty // Restore original model name if empty
const filePath = document.querySelector('#checkpointModal .modal-content') // Use the passed filePath to find the card
.querySelector('.file-path').textContent +
document.querySelector('#checkpointModal .modal-content')
.querySelector('#file-name').textContent;
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`); const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
if (checkpointCard) { if (checkpointCard) {
this.textContent = checkpointCard.dataset.model_name; this.textContent = checkpointCard.dataset.model_name;
@@ -91,10 +90,7 @@ export function setupModelNameEditing() {
modelNameContent.addEventListener('keydown', function(e) { modelNameContent.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
const filePath = document.querySelector('#checkpointModal .modal-content') // Use the passed filePath
.querySelector('.file-path').textContent +
document.querySelector('#checkpointModal .modal-content')
.querySelector('#file-name').textContent;
saveModelName(filePath); saveModelName(filePath);
this.blur(); this.blur();
} }
@@ -142,22 +138,15 @@ async function saveModelName(filePath) {
try { try {
await saveModelMetadata(filePath, { model_name: newModelName }); await saveModelMetadata(filePath, { model_name: newModelName });
// Update the corresponding checkpoint card's dataset and display // Update the card with the new model name
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`); updateCheckpointCard(filePath, { name: newModelName });
if (checkpointCard) {
checkpointCard.dataset.model_name = newModelName;
const titleElement = checkpointCard.querySelector('.card-title');
if (titleElement) {
titleElement.textContent = newModelName;
}
}
showToast('Model name updated successfully', 'success'); showToast('Model name updated successfully', 'success');
// Reload the page to reflect the sorted order // No need to reload the entire page
setTimeout(() => { // setTimeout(() => {
window.location.reload(); // window.location.reload();
}, 1500); // }, 1500);
} catch (error) { } catch (error) {
showToast('Failed to update model name', 'error'); showToast('Failed to update model name', 'error');
} }
@@ -165,8 +154,9 @@ async function saveModelName(filePath) {
/** /**
* Set up base model editing functionality * Set up base model editing functionality
* @param {string} filePath - The full file path of the model.
*/ */
export function setupBaseModelEditing() { export function setupBaseModelEditing(filePath) {
const baseModelContent = document.querySelector('.base-model-content'); const baseModelContent = document.querySelector('.base-model-content');
const editBtn = document.querySelector('.edit-base-model-btn'); const editBtn = document.querySelector('.edit-base-model-btn');
@@ -269,13 +259,7 @@ export function setupBaseModelEditing() {
// Only save if the value has actually changed // Only save if the value has actually changed
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) { if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
// Get file path for saving // Use the passed filePath for saving
const filePath = document.querySelector('#checkpointModal .modal-content')
.querySelector('.file-path').textContent +
document.querySelector('#checkpointModal .modal-content')
.querySelector('#file-name').textContent;
// Save the changes, passing the original value for comparison
saveBaseModel(filePath, originalValue); saveBaseModel(filePath, originalValue);
} }
@@ -323,11 +307,8 @@ async function saveBaseModel(filePath, originalValue) {
try { try {
await saveModelMetadata(filePath, { base_model: newBaseModel }); await saveModelMetadata(filePath, { base_model: newBaseModel });
// Update the corresponding checkpoint card's dataset // Update the card with the new base model
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`); updateCheckpointCard(filePath, { base_model: newBaseModel });
if (checkpointCard) {
checkpointCard.dataset.base_model = newBaseModel;
}
showToast('Base model updated successfully', 'success'); showToast('Base model updated successfully', 'success');
} catch (error) { } catch (error) {
@@ -337,8 +318,9 @@ async function saveBaseModel(filePath, originalValue) {
/** /**
* Set up file name editing functionality * Set up file name editing functionality
* @param {string} filePath - The full file path of the model.
*/ */
export function setupFileNameEditing() { export function setupFileNameEditing(filePath) {
const fileNameContent = document.querySelector('.file-name-content'); const fileNameContent = document.querySelector('.file-name-content');
const editBtn = document.querySelector('.edit-file-name-btn'); const editBtn = document.querySelector('.edit-file-name-btn');
@@ -440,10 +422,7 @@ export function setupFileNameEditing() {
} }
try { try {
// Get the full file path // Use the passed filePath (which includes the original filename)
const filePath = document.querySelector('#checkpointModal .modal-content')
.querySelector('.file-path').textContent + originalValue;
// Call API to rename the file // Call API to rename the file
const response = await fetch('/api/rename_checkpoint', { const response = await fetch('/api/rename_checkpoint', {
method: 'POST', method: 'POST',
@@ -451,7 +430,7 @@ export function setupFileNameEditing() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
file_path: filePath, file_path: filePath, // Use the full original path
new_file_name: newFileName new_file_name: newFileName
}) })
}); });
@@ -461,11 +440,24 @@ export function setupFileNameEditing() {
if (result.success) { if (result.success) {
showToast('File name updated successfully', 'success'); showToast('File name updated successfully', 'success');
// Get the new file path from the result
const pathParts = filePath.split(/[\\/]/);
pathParts.pop(); // Remove old filename
const newFilePath = [...pathParts, newFileName].join('/');
// Update the checkpoint card with new file path // Update the checkpoint card with new file path
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`); updateCheckpointCard(filePath, {
if (checkpointCard) { filepath: newFilePath,
const newFilePath = filePath.replace(originalValue, newFileName); file_name: newFileName
checkpointCard.dataset.filepath = newFilePath; });
// Update the file name display in the modal
document.querySelector('#file-name').textContent = newFileName;
// Update the modal's data-filepath attribute
const modalContent = document.querySelector('#checkpointModal .modal-content');
if (modalContent) {
modalContent.dataset.filepath = newFilePath;
} }
// Reload the page after a short delay to reflect changes // Reload the page after a short delay to reflect changes

View File

@@ -15,6 +15,7 @@ import {
saveModelMetadata saveModelMetadata
} from './ModelMetadata.js'; } from './ModelMetadata.js';
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js'; import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
import { updateCheckpointCard } from '../../utils/cardUpdater.js';
/** /**
* Display the checkpoint modal with the given checkpoint data * Display the checkpoint modal with the given checkpoint data
@@ -119,13 +120,13 @@ export function showCheckpointModal(checkpoint) {
`; `;
modalManager.showModal('checkpointModal', content); modalManager.showModal('checkpointModal', content);
setupEditableFields(); setupEditableFields(checkpoint.file_path);
setupShowcaseScroll(); setupShowcaseScroll();
setupTabSwitching(); setupTabSwitching();
setupTagTooltip(); setupTagTooltip();
setupModelNameEditing(); setupModelNameEditing(checkpoint.file_path);
setupBaseModelEditing(); setupBaseModelEditing(checkpoint.file_path);
setupFileNameEditing(); setupFileNameEditing(checkpoint.file_path);
// If we have a model ID but no description, fetch it // If we have a model ID but no description, fetch it
if (checkpoint.civitai?.modelId && !checkpoint.modelDescription) { if (checkpoint.civitai?.modelId && !checkpoint.modelDescription) {
@@ -135,8 +136,9 @@ export function showCheckpointModal(checkpoint) {
/** /**
* Set up editable fields in the checkpoint modal * Set up editable fields in the checkpoint modal
* @param {string} filePath - The full file path of the model.
*/ */
function setupEditableFields() { function setupEditableFields(filePath) {
const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
editableFields.forEach(field => { editableFields.forEach(field => {
@@ -165,10 +167,6 @@ function setupEditableFields() {
return; return;
} }
e.preventDefault(); e.preventDefault();
const filePath = document.querySelector('#checkpointModal .modal-content')
.querySelector('.file-path').textContent +
document.querySelector('#checkpointModal .modal-content')
.querySelector('#file-name').textContent;
await saveNotes(filePath); await saveNotes(filePath);
} }
}); });
@@ -185,10 +183,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
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`); updateCheckpointCard(filePath, { notes: content });
if (checkpointCard) {
checkpointCard.dataset.notes = content;
}
showToast('Notes saved successfully', 'success'); showToast('Notes saved successfully', 'success');
} catch (error) { } catch (error) {

View File

@@ -4,6 +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';
/** /**
* 保存模型元数据到服务器 * 保存模型元数据到服务器
@@ -32,13 +33,17 @@ export async function saveModelMetadata(filePath, data) {
/** /**
* 设置模型名称编辑功能 * 设置模型名称编辑功能
* @param {string} filePath - 文件路径
*/ */
export function setupModelNameEditing() { export function setupModelNameEditing(filePath) {
const modelNameContent = document.querySelector('.model-name-content'); const modelNameContent = document.querySelector('.model-name-content');
const editBtn = document.querySelector('.edit-model-name-btn'); const editBtn = document.querySelector('.edit-model-name-btn');
if (!modelNameContent || !editBtn) return; if (!modelNameContent || !editBtn) return;
// Store the file path in a data attribute for later use
modelNameContent.dataset.filePath = filePath;
// Show edit button on hover // Show edit button on hover
const modelNameHeader = document.querySelector('.model-name-header'); const modelNameHeader = document.querySelector('.model-name-header');
modelNameHeader.addEventListener('mouseenter', () => { modelNameHeader.addEventListener('mouseenter', () => {
@@ -76,10 +81,7 @@ export function setupModelNameEditing() {
if (this.textContent.trim() === '') { if (this.textContent.trim() === '') {
// Restore original model name if empty // Restore original model name if empty
const filePath = document.querySelector('#loraModal .modal-content') const filePath = this.dataset.filePath;
.querySelector('.file-path').textContent +
document.querySelector('#loraModal .modal-content')
.querySelector('#file-name').textContent + '.safetensors';
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
if (loraCard) { if (loraCard) {
this.textContent = loraCard.dataset.model_name; this.textContent = loraCard.dataset.model_name;
@@ -91,10 +93,7 @@ export function setupModelNameEditing() {
modelNameContent.addEventListener('keydown', function(e) { modelNameContent.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
const filePath = document.querySelector('#loraModal .modal-content') const filePath = this.dataset.filePath;
.querySelector('.file-path').textContent +
document.querySelector('#loraModal .modal-content')
.querySelector('#file-name').textContent + '.safetensors';
saveModelName(filePath); saveModelName(filePath);
this.blur(); this.blur();
} }
@@ -144,21 +143,9 @@ async function saveModelName(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
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); updateLoraCard(filePath, { model_name: newModelName });
if (loraCard) {
loraCard.dataset.model_name = newModelName;
const titleElement = loraCard.querySelector('.card-title');
if (titleElement) {
titleElement.textContent = newModelName;
}
}
showToast('Model name updated successfully', 'success'); showToast('Model name updated successfully', 'success');
// Reload the page to reflect the sorted order
setTimeout(() => {
window.location.reload();
}, 1500);
} catch (error) { } catch (error) {
showToast('Failed to update model name', 'error'); showToast('Failed to update model name', 'error');
} }
@@ -166,13 +153,17 @@ async function saveModelName(filePath) {
/** /**
* 设置基础模型编辑功能 * 设置基础模型编辑功能
* @param {string} filePath - 文件路径
*/ */
export function setupBaseModelEditing() { export function setupBaseModelEditing(filePath) {
const baseModelContent = document.querySelector('.base-model-content'); const baseModelContent = document.querySelector('.base-model-content');
const editBtn = document.querySelector('.edit-base-model-btn'); const editBtn = document.querySelector('.edit-base-model-btn');
if (!baseModelContent || !editBtn) return; if (!baseModelContent || !editBtn) return;
// Store the file path in a data attribute for later use
baseModelContent.dataset.filePath = filePath;
// Show edit button on hover // Show edit button on hover
const baseModelDisplay = document.querySelector('.base-model-display'); const baseModelDisplay = document.querySelector('.base-model-display');
baseModelDisplay.addEventListener('mouseenter', () => { baseModelDisplay.addEventListener('mouseenter', () => {
@@ -270,11 +261,8 @@ export function setupBaseModelEditing() {
// Only save if the value has actually changed // Only save if the value has actually changed
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) { if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
// Get file path for saving // Get file path from the dataset
const filePath = document.querySelector('#loraModal .modal-content') const filePath = baseModelContent.dataset.filePath;
.querySelector('.file-path').textContent +
document.querySelector('#loraModal .modal-content')
.querySelector('#file-name').textContent + '.safetensors';
// Save the changes, passing the original value for comparison // Save the changes, passing the original value for comparison
saveBaseModel(filePath, originalValue); saveBaseModel(filePath, originalValue);
@@ -325,10 +313,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
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); updateLoraCard(filePath, { base_model: newBaseModel });
if (loraCard) {
loraCard.dataset.base_model = newBaseModel;
}
showToast('Base model updated successfully', 'success'); showToast('Base model updated successfully', 'success');
} catch (error) { } catch (error) {
@@ -338,13 +323,17 @@ async function saveBaseModel(filePath, originalValue) {
/** /**
* 设置文件名编辑功能 * 设置文件名编辑功能
* @param {string} filePath - 文件路径
*/ */
export function setupFileNameEditing() { export function setupFileNameEditing(filePath) {
const fileNameContent = document.querySelector('.file-name-content'); const fileNameContent = document.querySelector('.file-name-content');
const editBtn = document.querySelector('.edit-file-name-btn'); const editBtn = document.querySelector('.edit-file-name-btn');
if (!fileNameContent || !editBtn) return; if (!fileNameContent || !editBtn) return;
// Store the original file path
fileNameContent.dataset.filePath = filePath;
// Show edit button on hover // Show edit button on hover
const fileNameWrapper = document.querySelector('.file-name-wrapper'); const fileNameWrapper = document.querySelector('.file-name-wrapper');
fileNameWrapper.addEventListener('mouseenter', () => { fileNameWrapper.addEventListener('mouseenter', () => {
@@ -441,9 +430,8 @@ export function setupFileNameEditing() {
} }
try { try {
// Get the full file path // Get the file path from the dataset
const filePath = document.querySelector('#loraModal .modal-content') const filePath = this.dataset.filePath;
.querySelector('.file-path').textContent + originalValue + '.safetensors';
// Call API to rename the file // Call API to rename the file
const response = await fetch('/api/rename_lora', { const response = await fetch('/api/rename_lora', {
@@ -462,12 +450,9 @@ export function setupFileNameEditing() {
if (result.success) { if (result.success) {
showToast('File name updated successfully', 'success'); showToast('File name updated successfully', 'success');
// Update the LoRA card with new file path // Get the new file path and update the card
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const newFilePath = filePath.replace(originalValue, newFileName);
if (loraCard) { updateLoraCard(filePath, {}, newFilePath);
const newFilePath = filePath.replace(originalValue, newFileName);
loraCard.dataset.filepath = newFilePath;
}
// Reload the page after a short delay to reflect changes // Reload the page after a short delay to reflect changes
setTimeout(() => { setTimeout(() => {

View File

@@ -18,6 +18,7 @@ import {
saveModelMetadata saveModelMetadata
} from './ModelMetadata.js'; } from './ModelMetadata.js';
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js'; import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
import { updateLoraCard } from '../../utils/cardUpdater.js';
/** /**
* 显示LoRA模型弹窗 * 显示LoRA模型弹窗
@@ -152,14 +153,14 @@ export function showLoraModal(lora) {
`; `;
modalManager.showModal('loraModal', content); modalManager.showModal('loraModal', content);
setupEditableFields(); setupEditableFields(lora.file_path);
setupShowcaseScroll(); setupShowcaseScroll();
setupTabSwitching(); setupTabSwitching();
setupTagTooltip(); setupTagTooltip();
setupTriggerWordsEditMode(); setupTriggerWordsEditMode();
setupModelNameEditing(); setupModelNameEditing(lora.file_path);
setupBaseModelEditing(); setupBaseModelEditing(lora.file_path);
setupFileNameEditing(); setupFileNameEditing(lora.file_path);
// If we have a model ID but no description, fetch it // If we have a model ID but no description, fetch it
if (lora.civitai?.modelId && !lora.modelDescription) { if (lora.civitai?.modelId && !lora.modelDescription) {
@@ -188,10 +189,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
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); updateLoraCard(filePath, { notes: content });
if (loraCard) {
loraCard.dataset.notes = content;
}
showToast('Notes saved successfully', 'success'); showToast('Notes saved successfully', 'success');
} catch (error) { } catch (error) {
@@ -199,7 +197,7 @@ window.saveNotes = async function(filePath) {
} }
}; };
function setupEditableFields() { function setupEditableFields(filePath) {
const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
editableFields.forEach(field => { editableFields.forEach(field => {
@@ -247,11 +245,6 @@ function setupEditableFields() {
if (!key || !value) return; if (!key || !value) return;
const filePath = document.querySelector('#loraModal .modal-content')
.querySelector('.file-path').textContent +
document.querySelector('#loraModal .modal-content')
.querySelector('#file-name').textContent + '.safetensors';
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
const currentPresets = parsePresets(loraCard.dataset.usage_tips); const currentPresets = parsePresets(loraCard.dataset.usage_tips);
@@ -262,7 +255,9 @@ function setupEditableFields() {
usage_tips: newPresetsJson usage_tips: newPresetsJson
}); });
loraCard.dataset.usage_tips = newPresetsJson; // Update the card with the new usage tips
updateLoraCard(filePath, { usage_tips: newPresetsJson });
presetTags.innerHTML = renderPresetTags(currentPresets); presetTags.innerHTML = renderPresetTags(currentPresets);
presetSelector.value = ''; presetSelector.value = '';
@@ -280,10 +275,6 @@ function setupEditableFields() {
return; return;
} }
e.preventDefault(); e.preventDefault();
const filePath = document.querySelector('#loraModal .modal-content')
.querySelector('.file-path').textContent +
document.querySelector('#loraModal .modal-content')
.querySelector('#file-name').textContent + '.safetensors';
await saveNotes(filePath); await saveNotes(filePath);
} }
}); });

View File

@@ -0,0 +1,123 @@
/**
* 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) {
// 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;
}
// Update card dataset and visual elements based on the updates object
Object.entries(updates).forEach(([key, value]) => {
// Update dataset
loraCard.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');
if (titleElement) titleElement.textContent = value;
// Also update the model name in the footer if it exists
const modelNameElement = loraCard.querySelector('.model-name');
if (modelNameElement) modelNameElement.textContent = value;
break;
case 'base_model':
// Update the base model label in the card header if it exists
const baseModelLabel = loraCard.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
}