mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: Add save metadata route and update checkpoint card functionality
This commit is contained in:
@@ -51,6 +51,7 @@ class CheckpointsRoutes:
|
||||
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/download', self.download_checkpoint)
|
||||
app.router.add_post('/api/checkpoints/save-metadata', self.save_metadata) # Add new route
|
||||
|
||||
async def get_checkpoints(self, request):
|
||||
"""Get paginated checkpoint data"""
|
||||
@@ -522,3 +523,44 @@ class CheckpointsRoutes:
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}, 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)
|
||||
|
||||
@@ -15,6 +15,7 @@ export function createCheckpointCard(checkpoint) {
|
||||
card.dataset.modified = checkpoint.modified;
|
||||
card.dataset.file_size = checkpoint.file_size;
|
||||
card.dataset.from_civitai = checkpoint.from_civitai;
|
||||
card.dataset.notes = checkpoint.notes || '';
|
||||
card.dataset.base_model = checkpoint.base_model || 'Unknown';
|
||||
|
||||
// Store metadata if available
|
||||
@@ -124,6 +125,7 @@ export function createCheckpointCard(checkpoint) {
|
||||
file_size: parseInt(card.dataset.file_size || '0'),
|
||||
from_civitai: card.dataset.from_civitai === 'true',
|
||||
base_model: card.dataset.base_model,
|
||||
notes: card.dataset.notes || '',
|
||||
preview_url: versionedPreviewUrl,
|
||||
// Parse civitai metadata from the card's dataset
|
||||
civitai: (() => {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
import { BASE_MODELS } from '../../utils/constants.js';
|
||||
import { updateCheckpointCard } from '../../utils/cardUpdater.js';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export async function saveModelMetadata(filePath, data) {
|
||||
const response = await fetch('/checkpoints/api/save-metadata', {
|
||||
const response = await fetch('/api/checkpoints/save-metadata', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -32,8 +33,9 @@ export async function saveModelMetadata(filePath, data) {
|
||||
|
||||
/**
|
||||
* 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 editBtn = document.querySelector('.edit-model-name-btn');
|
||||
|
||||
@@ -76,10 +78,7 @@ export function setupModelNameEditing() {
|
||||
|
||||
if (this.textContent.trim() === '') {
|
||||
// Restore original model name if empty
|
||||
const filePath = document.querySelector('#checkpointModal .modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('#checkpointModal .modal-content')
|
||||
.querySelector('#file-name').textContent;
|
||||
// Use the passed filePath to find the card
|
||||
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
|
||||
if (checkpointCard) {
|
||||
this.textContent = checkpointCard.dataset.model_name;
|
||||
@@ -91,10 +90,7 @@ export function setupModelNameEditing() {
|
||||
modelNameContent.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const filePath = document.querySelector('#checkpointModal .modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('#checkpointModal .modal-content')
|
||||
.querySelector('#file-name').textContent;
|
||||
// Use the passed filePath
|
||||
saveModelName(filePath);
|
||||
this.blur();
|
||||
}
|
||||
@@ -142,22 +138,15 @@ async function saveModelName(filePath) {
|
||||
try {
|
||||
await saveModelMetadata(filePath, { model_name: newModelName });
|
||||
|
||||
// Update the corresponding checkpoint card's dataset and display
|
||||
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
|
||||
if (checkpointCard) {
|
||||
checkpointCard.dataset.model_name = newModelName;
|
||||
const titleElement = checkpointCard.querySelector('.card-title');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = newModelName;
|
||||
}
|
||||
}
|
||||
// Update the card with the new model name
|
||||
updateCheckpointCard(filePath, { name: newModelName });
|
||||
|
||||
showToast('Model name updated successfully', 'success');
|
||||
|
||||
// Reload the page to reflect the sorted order
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
// No need to reload the entire page
|
||||
// setTimeout(() => {
|
||||
// window.location.reload();
|
||||
// }, 1500);
|
||||
} catch (error) {
|
||||
showToast('Failed to update model name', 'error');
|
||||
}
|
||||
@@ -165,8 +154,9 @@ async function saveModelName(filePath) {
|
||||
|
||||
/**
|
||||
* 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 editBtn = document.querySelector('.edit-base-model-btn');
|
||||
|
||||
@@ -269,13 +259,7 @@ export function setupBaseModelEditing() {
|
||||
|
||||
// Only save if the value has actually changed
|
||||
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
|
||||
// Get file path 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
|
||||
// Use the passed filePath for saving
|
||||
saveBaseModel(filePath, originalValue);
|
||||
}
|
||||
|
||||
@@ -323,11 +307,8 @@ async function saveBaseModel(filePath, originalValue) {
|
||||
try {
|
||||
await saveModelMetadata(filePath, { base_model: newBaseModel });
|
||||
|
||||
// Update the corresponding checkpoint card's dataset
|
||||
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
|
||||
if (checkpointCard) {
|
||||
checkpointCard.dataset.base_model = newBaseModel;
|
||||
}
|
||||
// Update the card with the new base model
|
||||
updateCheckpointCard(filePath, { base_model: newBaseModel });
|
||||
|
||||
showToast('Base model updated successfully', 'success');
|
||||
} catch (error) {
|
||||
@@ -337,8 +318,9 @@ async function saveBaseModel(filePath, originalValue) {
|
||||
|
||||
/**
|
||||
* 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 editBtn = document.querySelector('.edit-file-name-btn');
|
||||
|
||||
@@ -440,10 +422,7 @@ export function setupFileNameEditing() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the full file path
|
||||
const filePath = document.querySelector('#checkpointModal .modal-content')
|
||||
.querySelector('.file-path').textContent + originalValue;
|
||||
|
||||
// Use the passed filePath (which includes the original filename)
|
||||
// Call API to rename the file
|
||||
const response = await fetch('/api/rename_checkpoint', {
|
||||
method: 'POST',
|
||||
@@ -451,7 +430,7 @@ export function setupFileNameEditing() {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_path: filePath,
|
||||
file_path: filePath, // Use the full original path
|
||||
new_file_name: newFileName
|
||||
})
|
||||
});
|
||||
@@ -461,11 +440,24 @@ export function setupFileNameEditing() {
|
||||
if (result.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
|
||||
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
|
||||
if (checkpointCard) {
|
||||
const newFilePath = filePath.replace(originalValue, newFileName);
|
||||
checkpointCard.dataset.filepath = newFilePath;
|
||||
updateCheckpointCard(filePath, {
|
||||
filepath: newFilePath,
|
||||
file_name: newFileName
|
||||
});
|
||||
|
||||
// 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
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
saveModelMetadata
|
||||
} from './ModelMetadata.js';
|
||||
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
|
||||
import { updateCheckpointCard } from '../../utils/cardUpdater.js';
|
||||
|
||||
/**
|
||||
* Display the checkpoint modal with the given checkpoint data
|
||||
@@ -119,13 +120,13 @@ export function showCheckpointModal(checkpoint) {
|
||||
`;
|
||||
|
||||
modalManager.showModal('checkpointModal', content);
|
||||
setupEditableFields();
|
||||
setupEditableFields(checkpoint.file_path);
|
||||
setupShowcaseScroll();
|
||||
setupTabSwitching();
|
||||
setupTagTooltip();
|
||||
setupModelNameEditing();
|
||||
setupBaseModelEditing();
|
||||
setupFileNameEditing();
|
||||
setupModelNameEditing(checkpoint.file_path);
|
||||
setupBaseModelEditing(checkpoint.file_path);
|
||||
setupFileNameEditing(checkpoint.file_path);
|
||||
|
||||
// If we have a model ID but no description, fetch it
|
||||
if (checkpoint.civitai?.modelId && !checkpoint.modelDescription) {
|
||||
@@ -135,8 +136,9 @@ export function showCheckpointModal(checkpoint) {
|
||||
|
||||
/**
|
||||
* 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]');
|
||||
|
||||
editableFields.forEach(field => {
|
||||
@@ -165,10 +167,6 @@ function setupEditableFields() {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -185,10 +183,7 @@ async function saveNotes(filePath) {
|
||||
await saveModelMetadata(filePath, { notes: content });
|
||||
|
||||
// Update the corresponding checkpoint card's dataset
|
||||
const checkpointCard = document.querySelector(`.checkpoint-card[data-filepath="${filePath}"]`);
|
||||
if (checkpointCard) {
|
||||
checkpointCard.dataset.notes = content;
|
||||
}
|
||||
updateCheckpointCard(filePath, { notes: content });
|
||||
|
||||
showToast('Notes saved successfully', 'success');
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { showToast } from '../../utils/uiHelpers.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 editBtn = document.querySelector('.edit-model-name-btn');
|
||||
|
||||
if (!modelNameContent || !editBtn) return;
|
||||
|
||||
// Store the file path in a data attribute for later use
|
||||
modelNameContent.dataset.filePath = filePath;
|
||||
|
||||
// Show edit button on hover
|
||||
const modelNameHeader = document.querySelector('.model-name-header');
|
||||
modelNameHeader.addEventListener('mouseenter', () => {
|
||||
@@ -76,10 +81,7 @@ export function setupModelNameEditing() {
|
||||
|
||||
if (this.textContent.trim() === '') {
|
||||
// Restore original model name if empty
|
||||
const filePath = document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('#file-name').textContent + '.safetensors';
|
||||
const filePath = this.dataset.filePath;
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
this.textContent = loraCard.dataset.model_name;
|
||||
@@ -91,10 +93,7 @@ export function setupModelNameEditing() {
|
||||
modelNameContent.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const filePath = document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('#file-name').textContent + '.safetensors';
|
||||
const filePath = this.dataset.filePath;
|
||||
saveModelName(filePath);
|
||||
this.blur();
|
||||
}
|
||||
@@ -144,21 +143,9 @@ async function saveModelName(filePath) {
|
||||
await saveModelMetadata(filePath, { model_name: newModelName });
|
||||
|
||||
// Update the corresponding lora card's dataset and display
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
loraCard.dataset.model_name = newModelName;
|
||||
const titleElement = loraCard.querySelector('.card-title');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = newModelName;
|
||||
}
|
||||
}
|
||||
updateLoraCard(filePath, { model_name: newModelName });
|
||||
|
||||
showToast('Model name updated successfully', 'success');
|
||||
|
||||
// Reload the page to reflect the sorted order
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} catch (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 editBtn = document.querySelector('.edit-base-model-btn');
|
||||
|
||||
if (!baseModelContent || !editBtn) return;
|
||||
|
||||
// Store the file path in a data attribute for later use
|
||||
baseModelContent.dataset.filePath = filePath;
|
||||
|
||||
// Show edit button on hover
|
||||
const baseModelDisplay = document.querySelector('.base-model-display');
|
||||
baseModelDisplay.addEventListener('mouseenter', () => {
|
||||
@@ -270,11 +261,8 @@ export function setupBaseModelEditing() {
|
||||
|
||||
// Only save if the value has actually changed
|
||||
if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
|
||||
// Get file path for saving
|
||||
const filePath = document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('#file-name').textContent + '.safetensors';
|
||||
// Get file path from the dataset
|
||||
const filePath = baseModelContent.dataset.filePath;
|
||||
|
||||
// Save the changes, passing the original value for comparison
|
||||
saveBaseModel(filePath, originalValue);
|
||||
@@ -325,10 +313,7 @@ async function saveBaseModel(filePath, originalValue) {
|
||||
await saveModelMetadata(filePath, { base_model: newBaseModel });
|
||||
|
||||
// Update the corresponding lora card's dataset
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
loraCard.dataset.base_model = newBaseModel;
|
||||
}
|
||||
updateLoraCard(filePath, { base_model: newBaseModel });
|
||||
|
||||
showToast('Base model updated successfully', 'success');
|
||||
} 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 editBtn = document.querySelector('.edit-file-name-btn');
|
||||
|
||||
if (!fileNameContent || !editBtn) return;
|
||||
|
||||
// Store the original file path
|
||||
fileNameContent.dataset.filePath = filePath;
|
||||
|
||||
// Show edit button on hover
|
||||
const fileNameWrapper = document.querySelector('.file-name-wrapper');
|
||||
fileNameWrapper.addEventListener('mouseenter', () => {
|
||||
@@ -441,9 +430,8 @@ export function setupFileNameEditing() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the full file path
|
||||
const filePath = document.querySelector('#loraModal .modal-content')
|
||||
.querySelector('.file-path').textContent + originalValue + '.safetensors';
|
||||
// Get the file path from the dataset
|
||||
const filePath = this.dataset.filePath;
|
||||
|
||||
// Call API to rename the file
|
||||
const response = await fetch('/api/rename_lora', {
|
||||
@@ -462,12 +450,9 @@ export function setupFileNameEditing() {
|
||||
if (result.success) {
|
||||
showToast('File name updated successfully', 'success');
|
||||
|
||||
// Update the LoRA card with new file path
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
const newFilePath = filePath.replace(originalValue, newFileName);
|
||||
loraCard.dataset.filepath = newFilePath;
|
||||
}
|
||||
// Get the new file path and update the card
|
||||
const newFilePath = filePath.replace(originalValue, newFileName);
|
||||
updateLoraCard(filePath, {}, newFilePath);
|
||||
|
||||
// Reload the page after a short delay to reflect changes
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
saveModelMetadata
|
||||
} from './ModelMetadata.js';
|
||||
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
|
||||
import { updateLoraCard } from '../../utils/cardUpdater.js';
|
||||
|
||||
/**
|
||||
* 显示LoRA模型弹窗
|
||||
@@ -152,14 +153,14 @@ export function showLoraModal(lora) {
|
||||
`;
|
||||
|
||||
modalManager.showModal('loraModal', content);
|
||||
setupEditableFields();
|
||||
setupEditableFields(lora.file_path);
|
||||
setupShowcaseScroll();
|
||||
setupTabSwitching();
|
||||
setupTagTooltip();
|
||||
setupTriggerWordsEditMode();
|
||||
setupModelNameEditing();
|
||||
setupBaseModelEditing();
|
||||
setupFileNameEditing();
|
||||
setupModelNameEditing(lora.file_path);
|
||||
setupBaseModelEditing(lora.file_path);
|
||||
setupFileNameEditing(lora.file_path);
|
||||
|
||||
// If we have a model ID but no description, fetch it
|
||||
if (lora.civitai?.modelId && !lora.modelDescription) {
|
||||
@@ -188,10 +189,7 @@ window.saveNotes = async function(filePath) {
|
||||
await saveModelMetadata(filePath, { notes: content });
|
||||
|
||||
// Update the corresponding lora card's dataset
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
loraCard.dataset.notes = content;
|
||||
}
|
||||
updateLoraCard(filePath, { notes: content });
|
||||
|
||||
showToast('Notes saved successfully', 'success');
|
||||
} catch (error) {
|
||||
@@ -199,7 +197,7 @@ window.saveNotes = async function(filePath) {
|
||||
}
|
||||
};
|
||||
|
||||
function setupEditableFields() {
|
||||
function setupEditableFields(filePath) {
|
||||
const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
|
||||
|
||||
editableFields.forEach(field => {
|
||||
@@ -247,11 +245,6 @@ function setupEditableFields() {
|
||||
|
||||
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 currentPresets = parsePresets(loraCard.dataset.usage_tips);
|
||||
|
||||
@@ -262,7 +255,9 @@ function setupEditableFields() {
|
||||
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);
|
||||
|
||||
presetSelector.value = '';
|
||||
@@ -280,10 +275,6 @@ function setupEditableFields() {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
123
static/js/utils/cardUpdater.js
Normal file
123
static/js/utils/cardUpdater.js
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user