mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -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/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)
|
||||||
|
|||||||
@@ -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: (() => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
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