From b0a495b4f6eec9e8e0b9e1cf44d0d3f314557de7 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 26 Mar 2025 06:49:33 +0800 Subject: [PATCH] Add base model editing functionality to LoraModal - Introduced new styles for base model display and editing in lora-modal.css. - Enhanced LoraModal.js to support editing of the base model with a dropdown selector. - Implemented save functionality for the updated base model, including UI interactions for editing and saving changes. --- static/css/components/lora-modal.css | 53 +++++++++ static/js/components/LoraModal.js | 155 ++++++++++++++++++++++++++- 2 files changed, 206 insertions(+), 2 deletions(-) diff --git a/static/css/components/lora-modal.css b/static/css/components/lora-modal.css index 0e3245c0..c4278a29 100644 --- a/static/css/components/lora-modal.css +++ b/static/css/components/lora-modal.css @@ -573,6 +573,59 @@ flex: 2; /* 分配更多空间给base model */ } +/* Base model display and editing styles */ +.base-model-display { + display: flex; + align-items: center; + position: relative; +} + +.base-model-content { + padding: 2px 4px; + border-radius: var(--border-radius-xs); + border: 1px solid transparent; + color: var(--text-color); + flex: 1; +} + +.edit-base-model-btn { + background: transparent; + border: none; + color: var(--text-color); + opacity: 0; + cursor: pointer; + padding: 2px 5px; + border-radius: var(--border-radius-xs); + transition: all 0.2s ease; + margin-left: var(--space-1); +} + +.edit-base-model-btn.visible, +.base-model-display:hover .edit-base-model-btn { + opacity: 0.5; +} + +.edit-base-model-btn:hover { + opacity: 0.8 !important; + background: rgba(0, 0, 0, 0.05); +} + +[data-theme="dark"] .edit-base-model-btn:hover { + background: rgba(255, 255, 255, 0.05); +} + +.base-model-selector { + width: 100%; + padding: 3px 5px; + background: var(--bg-color); + border: 1px solid var(--lora-accent); + border-radius: var(--border-radius-xs); + color: var(--text-color); + font-size: 0.9em; + outline: none; + margin-right: var(--space-1); +} + .size-wrapper { flex: 1; border-left: 1px solid var(--lora-border); diff --git a/static/js/components/LoraModal.js b/static/js/components/LoraModal.js index b8254bff..bca3041e 100644 --- a/static/js/components/LoraModal.js +++ b/static/js/components/LoraModal.js @@ -1,7 +1,7 @@ import { showToast } from '../utils/uiHelpers.js'; import { state } from '../state/index.js'; import { modalManager } from '../managers/ModalManager.js'; -import { NSFW_LEVELS } from '../utils/constants.js'; +import { NSFW_LEVELS, BASE_MODELS } from '../utils/constants.js'; export function showLoraModal(lora) { const escapedWords = lora.civitai?.trainedWords?.length ? @@ -43,7 +43,12 @@ export function showLoraModal(lora) {
- ${lora.base_model || 'N/A'} +
+ ${lora.base_model || 'N/A'} + +
@@ -124,6 +129,7 @@ export function showLoraModal(lora) { setupTagTooltip(); setupTriggerWordsEditMode(); setupModelNameEditing(); + setupBaseModelEditing(); // If we have a model ID but no description, fetch it if (lora.civitai?.modelId && !lora.modelDescription) { @@ -1225,3 +1231,148 @@ function setupModelNameEditing() { } }); } + +// Add save model base model function +window.saveBaseModel = async function(filePath) { + const baseModelElement = document.querySelector('.base-model-content'); + const newBaseModel = baseModelElement.textContent.trim(); + + try { + 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; + } + + showToast('Base model updated successfully', 'success'); + } catch (error) { + showToast('Failed to update base model', 'error'); + } +}; + +// New function to handle base model editing +function setupBaseModelEditing() { + const baseModelContent = document.querySelector('.base-model-content'); + const editBtn = document.querySelector('.edit-base-model-btn'); + + if (!baseModelContent || !editBtn) return; + + // Show edit button on hover + const baseModelDisplay = document.querySelector('.base-model-display'); + baseModelDisplay.addEventListener('mouseenter', () => { + editBtn.classList.add('visible'); + }); + + baseModelDisplay.addEventListener('mouseleave', () => { + if (!baseModelDisplay.classList.contains('editing')) { + editBtn.classList.remove('visible'); + } + }); + + // Handle edit button click + editBtn.addEventListener('click', () => { + baseModelDisplay.classList.add('editing'); + + // Create dropdown selector to replace the base model content + const currentValue = baseModelContent.textContent.trim(); + const dropdown = document.createElement('select'); + dropdown.className = 'base-model-selector'; + + // Add options from BASE_MODELS constants + const baseModelCategories = { + 'Stable Diffusion 1.x': [BASE_MODELS.SD_1_4, BASE_MODELS.SD_1_5, BASE_MODELS.SD_1_5_LCM, BASE_MODELS.SD_1_5_HYPER], + 'Stable Diffusion 2.x': [BASE_MODELS.SD_2_0, BASE_MODELS.SD_2_1], + 'Stable Diffusion 3.x': [BASE_MODELS.SD_3, BASE_MODELS.SD_3_5, BASE_MODELS.SD_3_5_MEDIUM, BASE_MODELS.SD_3_5_LARGE, BASE_MODELS.SD_3_5_LARGE_TURBO], + 'SDXL': [BASE_MODELS.SDXL, BASE_MODELS.SDXL_LIGHTNING, BASE_MODELS.SDXL_HYPER], + 'Video Models': [BASE_MODELS.SVD, BASE_MODELS.WAN_VIDEO, BASE_MODELS.HUNYUAN_VIDEO], + 'Other Models': [ + BASE_MODELS.FLUX_1_D, BASE_MODELS.FLUX_1_S, BASE_MODELS.AURAFLOW, + BASE_MODELS.PIXART_A, BASE_MODELS.PIXART_E, BASE_MODELS.HUNYUAN_1, + BASE_MODELS.LUMINA, BASE_MODELS.KOLORS, BASE_MODELS.NOOBAI, + BASE_MODELS.ILLUSTRIOUS, BASE_MODELS.PONY, BASE_MODELS.UNKNOWN + ] + }; + + // Create option groups for better organization + Object.entries(baseModelCategories).forEach(([category, models]) => { + const group = document.createElement('optgroup'); + group.label = category; + + models.forEach(model => { + const option = document.createElement('option'); + option.value = model; + option.textContent = model; + option.selected = model === currentValue; + group.appendChild(option); + }); + + dropdown.appendChild(group); + }); + + // Replace content with dropdown + baseModelContent.style.display = 'none'; + baseModelDisplay.insertBefore(dropdown, editBtn); + + // Hide edit button during editing + editBtn.style.display = 'none'; + + // Focus the dropdown + dropdown.focus(); + + // Handle dropdown change + dropdown.addEventListener('change', function() { + const selectedModel = this.value; + baseModelContent.textContent = selectedModel; + }); + + // Function to save changes and exit edit mode + const saveAndExit = function() { + // Check if dropdown still exists and remove it + if (dropdown && dropdown.parentNode === baseModelDisplay) { + baseModelDisplay.removeChild(dropdown); + } + + // Show the content and edit button + baseModelContent.style.display = ''; + editBtn.style.display = ''; + + // Remove editing class + baseModelDisplay.classList.remove('editing'); + + // 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'; + + // Save the changes + saveBaseModel(filePath); + + // Remove this event listener + document.removeEventListener('click', outsideClickHandler); + }; + + // Handle outside clicks to save and exit + const outsideClickHandler = function(e) { + // If click is outside the dropdown and base model display + if (!baseModelDisplay.contains(e.target)) { + saveAndExit(); + } + }; + + // Add delayed event listener for outside clicks + setTimeout(() => { + document.addEventListener('click', outsideClickHandler); + }, 0); + + // Also handle dropdown blur event + dropdown.addEventListener('blur', function(e) { + // Only save if the related target is not the edit button or inside the baseModelDisplay + if (!baseModelDisplay.contains(e.relatedTarget)) { + saveAndExit(); + } + }); + }); +}