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