From 5fe5e7ea54b32ede688a561c50648ebe5e02734c Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 11 Oct 2025 19:43:22 +0800 Subject: [PATCH] feat(ui): enhance settings modal styling and add priority tags tabs - Rename `.settings-open-location-button` to `.settings-action-link` for better semantic meaning - Add enhanced hover/focus states with accent colors and border transitions - Implement tabbed interface for priority tags with LoRA, checkpoint, and embedding sections - Improve input styling with consistent error states and example code formatting - Remove deprecated grid layout in favor of tab-based organization - Add responsive tab navigation with proper focus management and visual feedback --- docs/priority_tags_help.md | 52 +++++++ locales/de.json | 6 +- locales/en.json | 16 +-- locales/es.json | 6 +- locales/fr.json | 6 +- locales/he.json | 6 +- locales/ja.json | 6 +- locales/ko.json | 6 +- locales/ru.json | 6 +- locales/zh-CN.json | 6 +- locales/zh-TW.json | 6 +- .../css/components/modal/settings-modal.css | 134 ++++++++++++++---- static/js/managers/HelpManager.js | 2 +- static/js/managers/SettingsManager.js | 24 +++- templates/components/modals/help_modal.html | 8 +- .../components/modals/settings_modal.html | 68 +++++---- 16 files changed, 259 insertions(+), 99 deletions(-) create mode 100644 docs/priority_tags_help.md diff --git a/docs/priority_tags_help.md b/docs/priority_tags_help.md new file mode 100644 index 00000000..6ef6b10b --- /dev/null +++ b/docs/priority_tags_help.md @@ -0,0 +1,52 @@ +# Priority Tags Configuration Guide + +This guide explains how to tailor the tag priority order that powers folder naming and tag suggestions in the LoRA Manager. You only need to edit the comma-separated list of entries shown in the **Priority Tags** field for each model type. + +## 1. Pick the Model Type + +In the **Priority Tags** dialog you will find one tab per model type (LoRA, Checkpoint, Embedding). Select the tab you want to update; changes on one tab do not affect the others. + +## 2. Edit the Entry List + +Inside the textarea you will see a line similar to: + +``` +character, concept, style(toon|toon_style) +``` + +This entire line is the **entry list**. Replace it with your own ordered list. + +### Entry Rules + +Each entry is separated by a comma, in order from highest to lowest priority: + +- **Canonical tag only:** `realistic` +- **Canonical tag with aliases:** `character(char|chars)` + +Aliases live inside `()` and are separated with `|`. The canonical name is what appears in folder names and UI suggestions when any of the aliases are detected. Matching is case-insensitive. + +## 3. Save the Settings + +After editing the entry list, press **Enter** to save. Use **Shift+Enter** whenever you need a new line. Clicking outside the field also saves automatically. A success toast confirms the update. + +## Examples + +| Goal | Entry List | +| --- | --- | +| Prefer people over styles | `character, portraits, style(anime\|toon)` | +| Group sci-fi variants | `sci-fi(scifi\|science_fiction), cyberpunk(cyber\|punk)` | +| Alias shorthand tags | `realistic(real\|realisim), photorealistic(photo_real)` | + +## Tips + +- Keep canonical names short and meaningful—they become folder names. +- Place the most important categories first; the first match wins. +- Avoid duplicate canonical names within the same list; only the first instance is used. + +## Troubleshooting + +- **Unexpected folder name?** Check that the canonical name you want is placed before other matches. +- **Alias not working?** Ensure the alias is inside parentheses and separated with `|`, e.g. `character(char|chars)`. +- **Validation error?** Look for missing parentheses or stray commas. Each entry must follow the `canonical(alias|alias)` pattern or just `canonical`. + +With these basics you can quickly adapt Priority Tags to match your library’s organization style. diff --git a/locales/de.json b/locales/de.json index 4c07fe58..599e0015 100644 --- a/locales/de.json +++ b/locales/de.json @@ -258,8 +258,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -267,7 +267,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/en.json b/locales/en.json index 89ece040..d33b74b5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -256,18 +256,18 @@ }, "priorityTags": { "title": "Priority Tags", - "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", - "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "description": "Customize the tag priority order for each model type.", + "placeholder": "character, concept, style(toon|toon_style)", + "example": "Example: character, concept, style(toon|toon_style)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { - "lora": "LoRA priority", - "checkpoint": "Checkpoint priority", - "embedding": "Embedding priority" + "lora": "LoRA", + "checkpoint": "Checkpoint", + "embedding": "Embedding" }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/es.json b/locales/es.json index 452c4b78..d58c1d53 100644 --- a/locales/es.json +++ b/locales/es.json @@ -258,8 +258,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -267,7 +267,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/fr.json b/locales/fr.json index b7f7a3e9..7ae95014 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/he.json b/locales/he.json index b29a94ac..95710ce8 100644 --- a/locales/he.json +++ b/locales/he.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/ja.json b/locales/ja.json index 38fa24af..4a8c6c27 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/ko.json b/locales/ko.json index 36cc513e..312c3446 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/ru.json b/locales/ru.json index 32242e82..b5d3d5d4 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 9437ac55..42c255ec 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 74ac065a..c7961e1e 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -351,8 +351,8 @@ "title": "Priority Tags", "description": "Customize the tag priority order for each model type. The first match becomes the folder name and powers tag suggestions.", "placeholder": "celebrity(celeb|celebrity), stylized, character(char)", - "help": "Separate entries with commas. Add aliases inside parentheses using the | symbol.", - "aliasHint": "Tip: Keep canonical names short and meaningful. You can use new lines to group related entries.", + "example": "Example: celebrity(celeb|celebrity), stylized, character(char)", + "helpLinkLabel": "Open priority tags help", "modelTypes": { "lora": "LoRA priority", "checkpoint": "Checkpoint priority", @@ -360,7 +360,7 @@ }, "saveSuccess": "Priority tags updated.", "saveError": "Failed to update priority tags.", - "loadingSuggestions": "Loading suggestions…", + "loadingSuggestions": "Loading suggestions...", "validation": { "missingClosingParen": "Entry {index} is missing a closing parenthesis.", "missingCanonical": "Entry {index} must include a canonical tag name.", diff --git a/static/css/components/modal/settings-modal.css b/static/css/components/modal/settings-modal.css index 04f6e11a..9f9a12fe 100644 --- a/static/css/components/modal/settings-modal.css +++ b/static/css/components/modal/settings-modal.css @@ -35,34 +35,38 @@ margin: 0; } -.settings-open-location-button { +.settings-action-link { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; - border: none; + border-radius: var(--border-radius-xs); + border: 1px solid transparent; background: none; color: var(--text-color); opacity: 0.6; cursor: pointer; - border-radius: var(--border-radius-xs); - transition: opacity 0.2s ease, background-color 0.2s ease; + text-decoration: none; + line-height: 1; + transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; } -.settings-open-location-button:hover, -.settings-open-location-button:focus-visible { +.settings-action-link:hover, +.settings-action-link:focus-visible { opacity: 1; + color: var(--lora-accent); background-color: rgba(var(--border-color-rgb, 148, 163, 184), 0.2); + border-color: rgba(var(--border-color-rgb, 148, 163, 184), 0.4); outline: none; } -.settings-open-location-button i { +.settings-action-link i { font-size: 1em; } -.settings-open-location-button:focus-visible { - box-shadow: 0 0 0 2px rgba(var(--border-color-rgb, 148, 163, 184), 0.6); +.settings-action-link:focus-visible { + box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.2); } /* Settings Links */ @@ -218,25 +222,8 @@ margin-top: var(--space-1); } -.priority-tags-grid { - display: grid; - gap: var(--space-2); -} - -@media (min-width: 640px) { - .priority-tags-grid { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - } -} - -.priority-tags-group { - display: flex; - flex-direction: column; - gap: var(--space-1); -} - .priority-tags-input { - width: 100%; + width: 97%; min-height: 72px; padding: 8px; border-radius: var(--border-radius-xs); @@ -252,12 +239,99 @@ box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1); } -.priority-tags-input.input-error { +.priority-tags-item { + gap: var(--space-2); +} + +.priority-tags-header { + align-items: center; +} + +.priority-tags-actions { + display: flex; + align-items: center; + justify-content: flex-end; +} + +.priority-tags-example { + font-size: 0.85em; + opacity: 0.8; + margin-top: var(--space-1); +} + +.priority-tags-example code { + font-family: var(--code-font, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace); + background-color: rgba(var(--lora-accent-rgb, 79, 70, 229), 0.12); + padding: 2px 6px; + border-radius: var(--border-radius-xs); + display: inline-block; +} + +.priority-tags-tabs { + position: relative; +} + +.priority-tags-tab-input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.priority-tags-tablist { + display: flex; + gap: var(--space-1); + border-bottom: 1px solid var(--border-color); + padding-bottom: var(--space-1); +} + +.priority-tags-tab-label { + flex: 1; + text-align: center; + padding: var(--space-1) var(--space-2); + border: none; + border-bottom: 2px solid transparent; + color: var(--text-color); + cursor: pointer; + transition: all 0.2s ease; + opacity: 0.7; +} + +.priority-tags-tab-label:hover, +.priority-tags-tab-label:focus { + opacity: 1; + color: var(--lora-accent); + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.05); +} + +.priority-tags-panels { + margin-top: var(--space-2); +} + +.priority-tags-panel { + display: none; +} + +#priority-tags-tab-lora:checked ~ .priority-tags-tablist label[for="priority-tags-tab-lora"], +#priority-tags-tab-checkpoint:checked ~ .priority-tags-tablist label[for="priority-tags-tab-checkpoint"], +#priority-tags-tab-embedding:checked ~ .priority-tags-tablist label[for="priority-tags-tab-embedding"] { + border-bottom-color: var(--lora-accent); + color: var(--lora-accent); + opacity: 1; + font-weight: 600; +} + +#priority-tags-tab-lora:checked ~ .priority-tags-panels #priority-tags-panel-lora, +#priority-tags-tab-checkpoint:checked ~ .priority-tags-panels #priority-tags-panel-checkpoint, +#priority-tags-tab-embedding:checked ~ .priority-tags-panels #priority-tags-panel-embedding { + display: block; +} + +.priority-tags-input.settings-input-error { border-color: var(--danger-color, #dc2626); box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.12); } -.input-error-message { +.settings-input-error-message { font-size: 0.8em; color: var(--danger-color, #dc2626); display: none; @@ -735,4 +809,4 @@ input:checked + .toggle-slider:before { padding-top: var(--space-2); margin-top: var(--space-2); } -} \ No newline at end of file +} diff --git a/static/js/managers/HelpManager.js b/static/js/managers/HelpManager.js index ec422ec2..807b3432 100644 --- a/static/js/managers/HelpManager.js +++ b/static/js/managers/HelpManager.js @@ -6,7 +6,7 @@ import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js'; export class HelpManager { constructor() { this.lastViewedTimestamp = getStorageItem('help_last_viewed', 0); - this.latestContentTimestamp = new Date('2025-07-09').getTime(); // Will be updated from server or config + this.latestContentTimestamp = new Date('2025-10-11').getTime(); // Will be updated from server or config this.isInitialized = false; // Default latest content data - could be fetched from server diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index 6d89d7fe..72d5b313 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -197,7 +197,7 @@ export class SettingsManager { button.addEventListener('click', () => this.toggleInputVisibility(button)); }); - const openSettingsLocationButton = document.querySelector('.settings-open-location-button'); + const openSettingsLocationButton = document.querySelector('.settings-open-location-trigger'); if (openSettingsLocationButton) { openSettingsLocationButton.addEventListener('click', () => { const filePath = openSettingsLocationButton.dataset.settingsPath; @@ -350,7 +350,8 @@ export class SettingsManager { } textarea.addEventListener('input', () => this.handlePriorityTagInput(modelType)); - textarea.addEventListener('blur', () => this.handlePriorityTagBlur(modelType)); + textarea.addEventListener('blur', () => this.handlePriorityTagSave(modelType)); + textarea.addEventListener('keydown', (event) => this.handlePriorityTagKeyDown(event, modelType)); }); } @@ -378,7 +379,20 @@ export class SettingsManager { this.displayPriorityTagValidation(modelType, validation.valid, validation.errors); } - async handlePriorityTagBlur(modelType) { + handlePriorityTagKeyDown(event, modelType) { + if (event.key !== 'Enter') { + return; + } + + if (event.shiftKey) { + return; + } + + event.preventDefault(); + this.handlePriorityTagSave(modelType); + } + + async handlePriorityTagSave(modelType) { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); if (!textarea) { return; @@ -423,7 +437,7 @@ export class SettingsManager { } if (isValid || errors.length === 0) { - textarea.classList.remove('input-error'); + textarea.classList.remove('settings-input-error'); if (errorElement) { errorElement.textContent = ''; errorElement.style.display = 'none'; @@ -431,7 +445,7 @@ export class SettingsManager { return; } - textarea.classList.add('input-error'); + textarea.classList.add('settings-input-error'); if (errorElement) { const message = this.getPriorityTagErrorMessage(errors[0]); errorElement.textContent = message; diff --git a/templates/components/modals/help_modal.html b/templates/components/modals/help_modal.html index 99fd97ca..20bf7354 100644 --- a/templates/components/modals/help_modal.html +++ b/templates/components/modals/help_modal.html @@ -108,6 +108,12 @@