mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -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
|
# Update cache
|
||||||
await self.scanner.update_single_lora_cache(file_path, file_path, metadata)
|
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})
|
return web.json_response({'success': True})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -430,25 +430,41 @@ class LoraScanner:
|
|||||||
# Remove old path from hash index if exists
|
# Remove old path from hash index if exists
|
||||||
self._hash_index.remove_by_path(original_path)
|
self._hash_index.remove_by_path(original_path)
|
||||||
|
|
||||||
|
# Remove the old entry from raw_data
|
||||||
cache.raw_data = [
|
cache.raw_data = [
|
||||||
item for item in cache.raw_data
|
item for item in cache.raw_data
|
||||||
if item['file_path'] != original_path
|
if item['file_path'] != original_path
|
||||||
]
|
]
|
||||||
|
|
||||||
if metadata:
|
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)
|
cache.raw_data.append(metadata)
|
||||||
|
|
||||||
# Update hash index with new path
|
# Update hash index with new path
|
||||||
if 'sha256' in metadata:
|
if 'sha256' in metadata:
|
||||||
self._hash_index.add_entry(metadata['sha256'], new_path)
|
self._hash_index.add_entry(metadata['sha256'], new_path)
|
||||||
|
|
||||||
all_folders = set(cache.folders)
|
# Update folders list
|
||||||
all_folders.add(metadata['folder'])
|
all_folders = set(item['folder'] for item in cache.raw_data)
|
||||||
cache.folders = sorted(list(all_folders), key=lambda x: x.lower())
|
cache.folders = sorted(list(all_folders), key=lambda x: x.lower())
|
||||||
|
|
||||||
# Resort cache
|
# Resort cache
|
||||||
await cache.resort()
|
await cache.resort()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
async def _update_metadata_paths(self, metadata_path: str, lora_path: str) -> Dict:
|
async def _update_metadata_paths(self, metadata_path: str, lora_path: str) -> Dict:
|
||||||
"""Update file paths in metadata file"""
|
"""Update file paths in metadata file"""
|
||||||
|
|||||||
@@ -425,4 +425,58 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
opacity: 0.9;
|
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">
|
<div class="modal-content">
|
||||||
<button class="close" onclick="modalManager.closeModal('loraModal')">×</button>
|
<button class="close" onclick="modalManager.closeModal('loraModal')">×</button>
|
||||||
<header class="modal-header">
|
<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>
|
</header>
|
||||||
|
|
||||||
<div class="modal-body">
|
<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() {
|
function setupEditableFields() {
|
||||||
const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
|
const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
|
||||||
|
|
||||||
@@ -113,11 +161,53 @@ function setupEditableFields() {
|
|||||||
|
|
||||||
field.addEventListener('blur', function() {
|
field.addEventListener('blur', function() {
|
||||||
if (this.textContent.trim() === '') {
|
if (this.textContent.trim() === '') {
|
||||||
this.textContent = this.classList.contains('usage-tips-content')
|
if (this.classList.contains('model-name-content')) {
|
||||||
? 'Save usage tips here..'
|
// Restore original model name if empty
|
||||||
: 'Add your notes here...';
|
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');
|
const presetSelector = document.getElementById('preset-selector');
|
||||||
|
|||||||
Reference in New Issue
Block a user