From 57ca4a275d46eaece411a60d94fec60d2c2cf02b Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 19 Feb 2025 22:33:34 +0800 Subject: [PATCH] Enhance LoraCard with preset controls for usage tips and update modal styles --- static/css/components/modal.css | 70 +++++++++++++++++ static/js/components/LoraCard.js | 130 +++++++++++++++++++++++++------ utils/models.py | 2 +- 3 files changed, 178 insertions(+), 24 deletions(-) diff --git a/static/css/components/modal.css b/static/css/components/modal.css index e99b9202..14df54d6 100644 --- a/static/css/components/modal.css +++ b/static/css/components/modal.css @@ -594,6 +594,7 @@ body.modal-open { word-break: break-word; } +.info-item.usage-tips, .info-item.notes { grid-column: 1 / -1 !important; /* Make notes section full width */ } @@ -843,4 +844,73 @@ body.modal-open { background: var(--lora-accent); color: white; transform: translateY(-2px); +} + +/* Update Preset Controls styles */ +.preset-controls { + display: flex; + gap: var(--space-2); + margin-bottom: var(--space-2); +} + +.preset-controls select, +.preset-controls input { + padding: var(--space-1); + background: var(--bg-color); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-xs); + color: var(--text-color); +} + +.preset-tags { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); +} + +.preset-tag { + display: flex; + align-items: center; + background: var(--lora-surface); + border: 1px solid var(--lora-border); + border-radius: var(--border-radius-xs); + padding: calc(var(--space-1) * 0.5) var(--space-1); + gap: var(--space-1); + transition: all 0.2s ease; +} + +.preset-tag span { + color: var(--lora-accent); + font-size: 0.9em; +} + +.preset-tag i { + color: var(--text-color); + opacity: 0.5; + cursor: pointer; + transition: all 0.2s ease; +} + +.preset-tag:hover { + background: oklch(var(--lora-accent) / 0.1); + border-color: var(--lora-accent); +} + +.preset-tag i:hover { + color: var(--lora-error); + opacity: 1; +} + +.add-preset-btn { + padding: calc(var(--space-1) * 0.5) var(--space-2); + background: var(--lora-accent); + color: var(--lora-text); + border: none; + border-radius: var(--border-radius-xs); + cursor: pointer; + transition: opacity 0.2s; +} + +.add-preset-btn:hover { + opacity: 0.9; } \ No newline at end of file diff --git a/static/js/components/LoraCard.js b/static/js/components/LoraCard.js index 1d5da545..7c1427fb 100644 --- a/static/js/components/LoraCard.js +++ b/static/js/components/LoraCard.js @@ -1,6 +1,5 @@ import { showToast } from '../utils/uiHelpers.js'; import { modalManager } from '../managers/ModalManager.js'; -import { resetAndReload } from '../api/loraApi.js'; import { state } from '../state/index.js'; export function createLoraCard(lora) { @@ -129,7 +128,7 @@ export function showLoraModal(lora) {
- ${lora.file_name || 'N/A'} + ${lora.file_name || 'N/A'}
@@ -142,10 +141,20 @@ export function showLoraModal(lora) {
-
${lora.usage_tips || 'Save usage tips here..'}
- +
+ + + +
+
+ ${renderPresetTags(parsePresets(lora.usage_tips))} +
${renderTriggerWords(escapedWords)} @@ -195,25 +204,57 @@ function setupEditableFields() { } }); }); -} -// Add these functions to handle saving the editable fields -window.saveUsageTips = async function(filePath) { - const content = document.querySelector('.usage-tips-content').textContent; - try { - await saveModelMetadata(filePath, { usage_tips: content }); + const presetSelector = document.getElementById('preset-selector'); + const presetValue = document.getElementById('preset-value'); + const addPresetBtn = document.querySelector('.add-preset-btn'); + const presetTags = document.querySelector('.preset-tags'); - // Update the corresponding lora card's dataset - const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); - if (loraCard) { - loraCard.dataset.usage_tips = content; + presetSelector.addEventListener('change', function() { + const selected = this.value; + if (selected) { + presetValue.style.display = 'inline-block'; + presetValue.min = selected.includes('strength') ? 0 : 1; + presetValue.max = selected.includes('strength') ? 1 : 12; + presetValue.step = selected.includes('strength') ? 0.01 : 1; + if (selected === 'clip_skip') { + presetValue.type = 'number'; + presetValue.step = 1; + } + } else { + presetValue.style.display = 'none'; } + }); - showToast('Usage tips saved successfully', 'success'); - } catch (error) { - showToast('Failed to save usage tips', 'error'); - } -}; + addPresetBtn.addEventListener('click', async function() { + const key = presetSelector.value; + const value = presetValue.value; + + if (!key || !value) return; + + 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}"]`); + const currentPresets = parsePresets(loraCard.dataset.usage_tips); + + currentPresets[key] = parseFloat(value); + const newPresetsJson = JSON.stringify(currentPresets); + + await saveModelMetadata(filePath, { + usage_tips: newPresetsJson + }); + + loraCard.dataset.usage_tips = newPresetsJson; + presetTags.innerHTML = renderPresetTags(currentPresets); + + presetSelector.value = ''; + presetValue.value = ''; + presetValue.style.display = 'none'; + }); +} window.saveNotes = async function(filePath) { const content = document.querySelector('.notes-content').textContent; @@ -225,7 +266,7 @@ window.saveNotes = async function(filePath) { if (loraCard) { loraCard.dataset.notes = content; } - + showToast('Notes saved successfully', 'success'); } catch (error) { showToast('Failed to save notes', 'error'); @@ -426,4 +467,47 @@ export function scrollToTop(button) { behavior: 'smooth' }); } -} \ No newline at end of file +} + +function parsePresets(usageTips) { + if (!usageTips || usageTips === 'Save usage tips here..') return {}; + try { + return JSON.parse(usageTips); + } catch { + return {}; + } +} + +function renderPresetTags(presets) { + return Object.entries(presets).map(([key, value]) => ` +
+ ${formatPresetKey(key)}: ${value} + +
+ `).join(''); +} + +function formatPresetKey(key) { + return key.split('_').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); +} + +window.removePreset = async function(key) { + 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}"]`); + const currentPresets = parsePresets(loraCard.dataset.usage_tips); + + delete currentPresets[key]; + const newPresetsJson = JSON.stringify(currentPresets); + + await saveModelMetadata(filePath, { + usage_tips: newPresetsJson + }); + + loraCard.dataset.usage_tips = newPresetsJson; + document.querySelector('.preset-tags').innerHTML = renderPresetTags(currentPresets); +}; \ No newline at end of file diff --git a/utils/models.py b/utils/models.py index 22af5f5b..0a39e065 100644 --- a/utils/models.py +++ b/utils/models.py @@ -15,7 +15,7 @@ class LoraMetadata: sha256: str # SHA256 hash of the file base_model: str # Base model (SD1.5/SD2.1/SDXL/etc.) preview_url: str # Preview image URL - usage_tips: str = "" # Usage tips for the model + usage_tips: str = "{}" # Usage tips for the model, json string notes: str = "" # Additional notes from_civitai: bool = True # Whether the lora is from Civitai civitai: Optional[Dict] = None # Civitai API data if available