Add inline model name editing with validation and resortable cache

This commit is contained in:
Will Miao
2025-03-07 10:32:27 +08:00
parent a9b3131e64
commit 69b1773ced
4 changed files with 172 additions and 7 deletions

View 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 */
}

View File

@@ -9,7 +9,12 @@ export function showLoraModal(lora) {
<div class="modal-content">
<button class="close" onclick="modalManager.closeModal('loraModal')">&times;</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');