mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Add inline model name editing with validation and resortable cache
This commit is contained in:
@@ -609,6 +609,11 @@ class ApiRoutes:
|
||||
# Update cache
|
||||
await self.scanner.update_single_lora_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:
|
||||
|
||||
@@ -430,25 +430,41 @@ class LoraScanner:
|
||||
# Remove old path from hash index if exists
|
||||
self._hash_index.remove_by_path(original_path)
|
||||
|
||||
# Remove the old entry from raw_data
|
||||
cache.raw_data = [
|
||||
item for item in cache.raw_data
|
||||
if item['file_path'] != original_path
|
||||
]
|
||||
|
||||
if metadata:
|
||||
metadata['folder'] = self._calculate_folder(new_path)
|
||||
# If this is an update to an existing path (not a move), ensure folder is preserved
|
||||
if original_path == new_path:
|
||||
# Find the folder from existing entries or calculate it
|
||||
existing_folder = next((item['folder'] for item in cache.raw_data
|
||||
if item['file_path'] == original_path), None)
|
||||
if existing_folder:
|
||||
metadata['folder'] = existing_folder
|
||||
else:
|
||||
metadata['folder'] = self._calculate_folder(new_path)
|
||||
else:
|
||||
# For moved files, recalculate the folder
|
||||
metadata['folder'] = self._calculate_folder(new_path)
|
||||
|
||||
# Add the updated metadata to raw_data
|
||||
cache.raw_data.append(metadata)
|
||||
|
||||
# Update hash index with new path
|
||||
if 'sha256' in metadata:
|
||||
self._hash_index.add_entry(metadata['sha256'], new_path)
|
||||
|
||||
all_folders = set(cache.folders)
|
||||
all_folders.add(metadata['folder'])
|
||||
# Update folders list
|
||||
all_folders = set(item['folder'] for item in cache.raw_data)
|
||||
cache.folders = sorted(list(all_folders), key=lambda x: x.lower())
|
||||
|
||||
# Resort cache
|
||||
await cache.resort()
|
||||
|
||||
return True
|
||||
|
||||
async def _update_metadata_paths(self, metadata_path: str, lora_path: str) -> Dict:
|
||||
"""Update file paths in metadata file"""
|
||||
|
||||
@@ -425,4 +425,58 @@
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Model name field styles - complete replacement */
|
||||
.model-name-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: calc(100% - 40px); /* Reduce width to avoid overlap with close button */
|
||||
position: relative; /* Add position relative for absolute positioning of save button */
|
||||
}
|
||||
|
||||
.model-name-field h2 {
|
||||
margin: 0;
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--border-radius-xs);
|
||||
transition: background-color 0.2s;
|
||||
flex: 1;
|
||||
font-size: 1.5em !important; /* Increased and forced size */
|
||||
font-weight: 600; /* Make it bolder */
|
||||
min-height: 1.5em;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
line-height: 1.2;
|
||||
color: var(--text-color); /* Ensure correct color */
|
||||
}
|
||||
|
||||
.model-name-field h2:hover {
|
||||
background: oklch(var(--lora-accent) / 0.1);
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.model-name-field h2:focus {
|
||||
outline: none;
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--lora-accent);
|
||||
}
|
||||
|
||||
.model-name-field .save-btn {
|
||||
position: absolute;
|
||||
right: 10px; /* Position closer to the end of the field */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.model-name-field:hover .save-btn,
|
||||
.model-name-field h2:focus ~ .save-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Ensure close button is accessible */
|
||||
.modal-content .close {
|
||||
z-index: 10; /* Ensure close button is above other elements */
|
||||
}
|
||||
@@ -9,7 +9,12 @@ export function showLoraModal(lora) {
|
||||
<div class="modal-content">
|
||||
<button class="close" onclick="modalManager.closeModal('loraModal')">×</button>
|
||||
<header class="modal-header">
|
||||
<h2>${lora.model_name}</h2>
|
||||
<div class="editable-field model-name-field">
|
||||
<h2 class="model-name-content" contenteditable="true" spellcheck="false">${lora.model_name}</h2>
|
||||
<button class="save-btn" onclick="saveModelName('${lora.file_path}')">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="modal-body">
|
||||
@@ -100,6 +105,49 @@ window.copyFileName = async function(fileName) {
|
||||
}
|
||||
};
|
||||
|
||||
// Add function to save model name
|
||||
window.saveModelName = async function(filePath) {
|
||||
const modelNameElement = document.querySelector('.model-name-content');
|
||||
const newModelName = modelNameElement.textContent.trim();
|
||||
|
||||
// Validate model name
|
||||
if (!newModelName) {
|
||||
showToast('Model name cannot be empty', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if model name is too long (limit to 100 characters)
|
||||
if (newModelName.length > 100) {
|
||||
showToast('Model name is too long (maximum 100 characters)', 'error');
|
||||
// Truncate the displayed text
|
||||
modelNameElement.textContent = newModelName.substring(0, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
function setupEditableFields() {
|
||||
const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
|
||||
|
||||
@@ -113,11 +161,53 @@ function setupEditableFields() {
|
||||
|
||||
field.addEventListener('blur', function() {
|
||||
if (this.textContent.trim() === '') {
|
||||
this.textContent = this.classList.contains('usage-tips-content')
|
||||
? 'Save usage tips here..'
|
||||
: 'Add your notes here...';
|
||||
if (this.classList.contains('model-name-content')) {
|
||||
// Restore original model name if empty
|
||||
const filePath = document.querySelector('.modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('.modal-content')
|
||||
.querySelector('#file-name').textContent + '.safetensors';
|
||||
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||
if (loraCard) {
|
||||
this.textContent = loraCard.dataset.model_name;
|
||||
}
|
||||
} else if (this.classList.contains('usage-tips-content')) {
|
||||
this.textContent = 'Save usage tips here..';
|
||||
} else {
|
||||
this.textContent = 'Add your notes here...';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add input validation for model name
|
||||
if (field.classList.contains('model-name-content')) {
|
||||
field.addEventListener('input', function() {
|
||||
// Limit model name length
|
||||
if (this.textContent.length > 100) {
|
||||
this.textContent = this.textContent.substring(0, 100);
|
||||
// Place cursor at the end
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.setStart(this.childNodes[0], 100);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
showToast('Model name is limited to 100 characters', 'warning');
|
||||
}
|
||||
});
|
||||
|
||||
field.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const filePath = document.querySelector('.modal-content')
|
||||
.querySelector('.file-path').textContent +
|
||||
document.querySelector('.modal-content')
|
||||
.querySelector('#file-name').textContent + '.safetensors';
|
||||
saveModelName(filePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const presetSelector = document.getElementById('preset-selector');
|
||||
|
||||
Reference in New Issue
Block a user