mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 15:38:52 -03:00
feat: Implement download path template settings and base model path mappings in UI
This commit is contained in:
@@ -691,14 +691,14 @@ input:checked + .toggle-slider:before {
|
|||||||
color: var(--lora-warning, #f39c12);
|
color: var(--lora-warning, #f39c12);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add styles for density description list */
|
/* Add styles for list description */
|
||||||
.density-description {
|
.list-description {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.density-description li {
|
.list-description li {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,3 +1136,149 @@ input:checked + .toggle-slider:before {
|
|||||||
[data-theme="dark"] .example-option-btn:hover {
|
[data-theme="dark"] .example-option-btn:hover {
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Path Template Settings Styles */
|
||||||
|
.template-preview {
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
padding: var(--space-1);
|
||||||
|
margin-top: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: var(--lora-accent);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .template-preview {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid var(--lora-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-preview:before {
|
||||||
|
content: "Preview: ";
|
||||||
|
opacity: 0.7;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base Model Mappings Styles - Updated to match other settings */
|
||||||
|
.mappings-container {
|
||||||
|
border: 1px solid var(--lora-border);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
padding: var(--space-2);
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
margin-top: 8px; /* Add consistent spacing */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mappings-container {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-mapping-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: all 0.2s;
|
||||||
|
height: 32px; /* Match other control heights */
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-mapping-btn:hover {
|
||||||
|
background: oklch(from var(--lora-accent) l c h / 85%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-row {
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr auto;
|
||||||
|
gap: var(--space-1);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-select,
|
||||||
|
.path-value-input {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--lora-surface);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.9em;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-value-input {
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base-model-select:focus,
|
||||||
|
.path-value-input:focus {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-mapping-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--lora-error);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--lora-error);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-mapping-btn:hover {
|
||||||
|
background: var(--lora-error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-3);
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for mapping controls */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.mapping-controls {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-mapping-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
justify-self: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme specific adjustments */
|
||||||
|
[data-theme="dark"] .base-model-select,
|
||||||
|
[data-theme="dark"] .path-value-input {
|
||||||
|
background-color: rgba(30, 30, 30, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .base-model-select option {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { showToast } from '../utils/uiHelpers.js';
|
|||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { resetAndReload } from '../api/loraApi.js';
|
import { resetAndReload } from '../api/loraApi.js';
|
||||||
import { setStorageItem, getStorageItem } from '../utils/storageHelpers.js';
|
import { setStorageItem, getStorageItem } from '../utils/storageHelpers.js';
|
||||||
|
import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS } from '../utils/constants.js';
|
||||||
|
|
||||||
export class SettingsManager {
|
export class SettingsManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -56,6 +57,16 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
// We can delete the old setting, but keeping it for backwards compatibility
|
// We can delete the old setting, but keeping it for backwards compatibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set default for download path template if undefined
|
||||||
|
if (state.global.settings.download_path_template === undefined) {
|
||||||
|
state.global.settings.download_path_template = DOWNLOAD_PATH_TEMPLATES.BASE_MODEL_TAG.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default for base model path mappings if undefined
|
||||||
|
if (state.global.settings.base_model_path_mappings === undefined) {
|
||||||
|
state.global.settings.base_model_path_mappings = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@@ -125,6 +136,16 @@ export class SettingsManager {
|
|||||||
optimizeExampleImagesCheckbox.checked = state.global.settings.optimizeExampleImages || false;
|
optimizeExampleImagesCheckbox.checked = state.global.settings.optimizeExampleImages || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set download path template setting
|
||||||
|
const downloadPathTemplateSelect = document.getElementById('downloadPathTemplate');
|
||||||
|
if (downloadPathTemplateSelect) {
|
||||||
|
downloadPathTemplateSelect.value = state.global.settings.download_path_template || '';
|
||||||
|
this.updatePathTemplatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load base model path mappings
|
||||||
|
this.loadBaseModelMappings();
|
||||||
|
|
||||||
// Load default lora root
|
// Load default lora root
|
||||||
await this.loadLoraRoots();
|
await this.loadLoraRoots();
|
||||||
|
|
||||||
@@ -212,6 +233,197 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadBaseModelMappings() {
|
||||||
|
const mappingsContainer = document.getElementById('baseModelMappingsContainer');
|
||||||
|
if (!mappingsContainer) return;
|
||||||
|
|
||||||
|
const mappings = state.global.settings.base_model_path_mappings || {};
|
||||||
|
|
||||||
|
// Clear existing mappings
|
||||||
|
mappingsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Add existing mappings
|
||||||
|
Object.entries(mappings).forEach(([baseModel, pathValue]) => {
|
||||||
|
this.addMappingRow(baseModel, pathValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add empty row for new mappings if none exist
|
||||||
|
if (Object.keys(mappings).length === 0) {
|
||||||
|
this.addMappingRow('', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMappingRow(baseModel = '', pathValue = '') {
|
||||||
|
const mappingsContainer = document.getElementById('baseModelMappingsContainer');
|
||||||
|
if (!mappingsContainer) return;
|
||||||
|
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'mapping-row';
|
||||||
|
|
||||||
|
const availableModels = MAPPABLE_BASE_MODELS.filter(model => {
|
||||||
|
const existingMappings = state.global.settings.base_model_path_mappings || {};
|
||||||
|
return !existingMappings.hasOwnProperty(model) || model === baseModel;
|
||||||
|
});
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<div class="mapping-controls">
|
||||||
|
<select class="base-model-select">
|
||||||
|
<option value="">Select Base Model</option>
|
||||||
|
${availableModels.map(model =>
|
||||||
|
`<option value="${model}" ${model === baseModel ? 'selected' : ''}>${model}</option>`
|
||||||
|
).join('')}
|
||||||
|
</select>
|
||||||
|
<input type="text" class="path-value-input" placeholder="Custom path (e.g., flux)" value="${pathValue}">
|
||||||
|
<button type="button" class="remove-mapping-btn" title="Remove mapping">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
const baseModelSelect = row.querySelector('.base-model-select');
|
||||||
|
const pathValueInput = row.querySelector('.path-value-input');
|
||||||
|
const removeBtn = row.querySelector('.remove-mapping-btn');
|
||||||
|
|
||||||
|
// Save on select change immediately
|
||||||
|
baseModelSelect.addEventListener('change', () => this.updateBaseModelMappings());
|
||||||
|
|
||||||
|
// Save on input blur or Enter key
|
||||||
|
pathValueInput.addEventListener('blur', () => this.updateBaseModelMappings());
|
||||||
|
pathValueInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.target.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removeBtn.addEventListener('click', () => {
|
||||||
|
row.remove();
|
||||||
|
this.updateBaseModelMappings();
|
||||||
|
});
|
||||||
|
|
||||||
|
mappingsContainer.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBaseModelMappings() {
|
||||||
|
const mappingsContainer = document.getElementById('baseModelMappingsContainer');
|
||||||
|
if (!mappingsContainer) return;
|
||||||
|
|
||||||
|
const rows = mappingsContainer.querySelectorAll('.mapping-row');
|
||||||
|
const newMappings = {};
|
||||||
|
let hasValidMapping = false;
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const baseModelSelect = row.querySelector('.base-model-select');
|
||||||
|
const pathValueInput = row.querySelector('.path-value-input');
|
||||||
|
|
||||||
|
const baseModel = baseModelSelect.value.trim();
|
||||||
|
const pathValue = pathValueInput.value.trim();
|
||||||
|
|
||||||
|
if (baseModel && pathValue) {
|
||||||
|
newMappings[baseModel] = pathValue;
|
||||||
|
hasValidMapping = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if mappings have actually changed
|
||||||
|
const currentMappings = state.global.settings.base_model_path_mappings || {};
|
||||||
|
const mappingsChanged = JSON.stringify(currentMappings) !== JSON.stringify(newMappings);
|
||||||
|
|
||||||
|
if (mappingsChanged) {
|
||||||
|
// Update state and save
|
||||||
|
state.global.settings.base_model_path_mappings = newMappings;
|
||||||
|
this.saveBaseModelMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add empty row if no valid mappings exist
|
||||||
|
const hasEmptyRow = Array.from(rows).some(row => {
|
||||||
|
const baseModelSelect = row.querySelector('.base-model-select');
|
||||||
|
const pathValueInput = row.querySelector('.path-value-input');
|
||||||
|
return !baseModelSelect.value && !pathValueInput.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasEmptyRow) {
|
||||||
|
this.addMappingRow('', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available options in all selects
|
||||||
|
this.updateAvailableBaseModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAvailableBaseModels() {
|
||||||
|
const mappingsContainer = document.getElementById('baseModelMappingsContainer');
|
||||||
|
if (!mappingsContainer) return;
|
||||||
|
|
||||||
|
const existingMappings = state.global.settings.base_model_path_mappings || {};
|
||||||
|
const rows = mappingsContainer.querySelectorAll('.mapping-row');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const select = row.querySelector('.base-model-select');
|
||||||
|
const currentValue = select.value;
|
||||||
|
|
||||||
|
// Get available models (not already mapped, except current)
|
||||||
|
const availableModels = MAPPABLE_BASE_MODELS.filter(model =>
|
||||||
|
!existingMappings.hasOwnProperty(model) || model === currentValue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rebuild options
|
||||||
|
select.innerHTML = '<option value="">Select Base Model</option>' +
|
||||||
|
availableModels.map(model =>
|
||||||
|
`<option value="${model}" ${model === currentValue ? 'selected' : ''}>${model}</option>`
|
||||||
|
).join('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBaseModelMappings() {
|
||||||
|
try {
|
||||||
|
// Save to localStorage
|
||||||
|
setStorageItem('settings', state.global.settings);
|
||||||
|
|
||||||
|
// Save to backend
|
||||||
|
const response = await fetch('/api/settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
base_model_path_mappings: JSON.stringify(state.global.settings.base_model_path_mappings)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save base model mappings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success toast
|
||||||
|
const mappingCount = Object.keys(state.global.settings.base_model_path_mappings).length;
|
||||||
|
if (mappingCount > 0) {
|
||||||
|
showToast(`Base model path mappings updated (${mappingCount} mapping${mappingCount !== 1 ? 's' : ''})`, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Base model path mappings cleared', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving base model mappings:', error);
|
||||||
|
showToast('Failed to save base model mappings: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePathTemplatePreview() {
|
||||||
|
const templateSelect = document.getElementById('downloadPathTemplate');
|
||||||
|
const previewElement = document.getElementById('pathTemplatePreview');
|
||||||
|
if (!templateSelect || !previewElement) return;
|
||||||
|
|
||||||
|
const template = templateSelect.value;
|
||||||
|
const templateInfo = Object.values(DOWNLOAD_PATH_TEMPLATES).find(t => t.value === template);
|
||||||
|
|
||||||
|
if (templateInfo) {
|
||||||
|
previewElement.textContent = templateInfo.example;
|
||||||
|
previewElement.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
previewElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggleSettings() {
|
toggleSettings() {
|
||||||
if (this.isOpen) {
|
if (this.isOpen) {
|
||||||
modalManager.closeModal('settingsModal');
|
modalManager.closeModal('settingsModal');
|
||||||
@@ -221,9 +433,6 @@ export class SettingsManager {
|
|||||||
this.isOpen = !this.isOpen;
|
this.isOpen = !this.isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-save methods for different control types
|
|
||||||
|
|
||||||
// For toggle switches
|
|
||||||
async saveToggleSetting(elementId, settingKey) {
|
async saveToggleSetting(elementId, settingKey) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
@@ -288,7 +497,6 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For select dropdowns
|
|
||||||
async saveSelectSetting(elementId, settingKey) {
|
async saveSelectSetting(elementId, settingKey) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
@@ -307,6 +515,9 @@ export class SettingsManager {
|
|||||||
state.global.settings.compactMode = (value !== 'default');
|
state.global.settings.compactMode = (value !== 'default');
|
||||||
} else if (settingKey === 'card_info_display') {
|
} else if (settingKey === 'card_info_display') {
|
||||||
state.global.settings.cardInfoDisplay = value;
|
state.global.settings.cardInfoDisplay = value;
|
||||||
|
} else if (settingKey === 'download_path_template') {
|
||||||
|
state.global.settings.download_path_template = value;
|
||||||
|
this.updatePathTemplatePreview();
|
||||||
} else {
|
} else {
|
||||||
// For any other settings that might be added in the future
|
// For any other settings that might be added in the future
|
||||||
state.global.settings[settingKey] = value;
|
state.global.settings[settingKey] = value;
|
||||||
@@ -317,7 +528,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// For backend settings, make API call
|
// For backend settings, make API call
|
||||||
if (settingKey === 'default_lora_root' || settingKey === 'default_checkpoint_root') {
|
if (settingKey === 'default_lora_root' || settingKey === 'default_checkpoint_root' || settingKey === 'download_path_template') {
|
||||||
const payload = {};
|
const payload = {};
|
||||||
payload[settingKey] = value;
|
payload[settingKey] = value;
|
||||||
|
|
||||||
@@ -355,7 +566,6 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For input fields
|
|
||||||
async saveInputSetting(elementId, settingKey) {
|
async saveInputSetting(elementId, settingKey) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|||||||
@@ -40,10 +40,13 @@ export const BASE_MODELS = {
|
|||||||
SVD: "SVD",
|
SVD: "SVD",
|
||||||
LTXV: "LTXV",
|
LTXV: "LTXV",
|
||||||
WAN_VIDEO: "Wan Video",
|
WAN_VIDEO: "Wan Video",
|
||||||
|
WAN_VIDEO_1_3B_T2V: "Wan Video 1.3B t2v",
|
||||||
|
WAN_VIDEO_14B_T2V: "Wan Video 14B t2v",
|
||||||
|
WAN_VIDEO_14B_I2V_480P: "Wan Video 14B i2v 480p",
|
||||||
|
WAN_VIDEO_14B_I2V_720P: "Wan Video 14B i2v 720p",
|
||||||
HUNYUAN_VIDEO: "Hunyuan Video",
|
HUNYUAN_VIDEO: "Hunyuan Video",
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
UNKNOWN: "Unknown"
|
UNKNOWN: "Other"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Base model display names and their corresponding class names (for styling)
|
// Base model display names and their corresponding class names (for styling)
|
||||||
@@ -95,6 +98,37 @@ export const BASE_MODEL_CLASSES = {
|
|||||||
[BASE_MODELS.UNKNOWN]: "unknown"
|
[BASE_MODELS.UNKNOWN]: "unknown"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Path template constants for download organization
|
||||||
|
export const DOWNLOAD_PATH_TEMPLATES = {
|
||||||
|
FLAT: {
|
||||||
|
value: '',
|
||||||
|
label: 'Flat Structure',
|
||||||
|
description: 'Download directly to root folder',
|
||||||
|
example: 'model-name.safetensors'
|
||||||
|
},
|
||||||
|
BASE_MODEL: {
|
||||||
|
value: '{base_model}',
|
||||||
|
label: 'By Base Model',
|
||||||
|
description: 'Organize by base model type',
|
||||||
|
example: 'Flux.1 D/model-name.safetensors'
|
||||||
|
},
|
||||||
|
FIRST_TAG: {
|
||||||
|
value: '{first_tag}',
|
||||||
|
label: 'By First Tag',
|
||||||
|
description: 'Organize by primary tag/category',
|
||||||
|
example: 'style/model-name.safetensors'
|
||||||
|
},
|
||||||
|
BASE_MODEL_TAG: {
|
||||||
|
value: '{base_model}/{first_tag}',
|
||||||
|
label: 'Base Model + First Tag',
|
||||||
|
description: 'Organize by base model and primary tag',
|
||||||
|
example: 'Flux.1 D/style/model-name.safetensors'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base models available for path mapping (for UI selection)
|
||||||
|
export const MAPPABLE_BASE_MODELS = Object.values(BASE_MODELS).sort();
|
||||||
|
|
||||||
export const NSFW_LEVELS = {
|
export const NSFW_LEVELS = {
|
||||||
UNKNOWN: 0,
|
UNKNOWN: 0,
|
||||||
PG: 1,
|
PG: 1,
|
||||||
|
|||||||
@@ -177,6 +177,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Video Settings Section -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Video Settings</h3>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="autoplayOnHover">Autoplay Videos on Hover</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="autoplayOnHover"
|
||||||
|
onchange="settingsManager.saveToggleSetting('autoplayOnHover', 'autoplay_on_hover')">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
Only play video previews when hovering over them
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Video Settings Section -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Video Settings</h3>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="autoplayOnHover">Autoplay Videos on Hover</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="autoplayOnHover"
|
||||||
|
onchange="settingsManager.saveToggleSetting('autoplayOnHover', 'autoplay_on_hover')">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
Only play video previews when hovering over them
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Folder Settings Section -->
|
<!-- Add Folder Settings Section -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Folder Settings</h3>
|
<h3>Folder Settings</h3>
|
||||||
@@ -216,6 +262,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Default Path Customization Section -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Default Path Customization</h3>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="downloadPathTemplate">Download Path Template</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control select-control">
|
||||||
|
<select id="downloadPathTemplate" onchange="settingsManager.saveSelectSetting('downloadPathTemplate', 'download_path_template')">
|
||||||
|
<option value="">Flat Structure</option>
|
||||||
|
<option value="{base_model}">By Base Model</option>
|
||||||
|
<option value="{first_tag}">By First Tag</option>
|
||||||
|
<option value="{base_model}/{first_tag}">Base Model + First Tag</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
"Configure path structure for default download locations
|
||||||
|
<ul class="list-description">
|
||||||
|
<li><strong>Flat:</strong> All models in root folder</li>
|
||||||
|
<li><strong>Base Model:</strong> Organized by model type (e.g., Flux.1 D, SDXL)</li>
|
||||||
|
<li><strong>First Tag:</strong> Organized by primary tag (e.g., style, character)</li>
|
||||||
|
<li><strong>Base Model + Tag:</strong> Two-level organization for better structure</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="pathTemplatePreview" class="template-preview"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label>Base Model Path Mappings</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<button type="button" class="add-mapping-btn" onclick="settingsManager.addMappingRow()">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
<span>Add Mapping</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-help">
|
||||||
|
Customize folder names for specific base models (e.g., "Flux.1 D" → "flux")
|
||||||
|
</div>
|
||||||
|
<div class="mappings-container">
|
||||||
|
<div id="baseModelMappingsContainer">
|
||||||
|
<!-- Mapping rows will be added dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Add Layout Settings Section -->
|
<!-- Add Layout Settings Section -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Layout Settings</h3>
|
<h3>Layout Settings</h3>
|
||||||
@@ -235,7 +334,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-help">
|
<div class="input-help">
|
||||||
Choose how many cards to display per row:
|
Choose how many cards to display per row:
|
||||||
<ul class="density-description">
|
<ul class="list-description">
|
||||||
<li><strong>Default:</strong> 5 (1080p), 6 (2K), 8 (4K)</li>
|
<li><strong>Default:</strong> 5 (1080p), 6 (2K), 8 (4K)</li>
|
||||||
<li><strong>Medium:</strong> 6 (1080p), 7 (2K), 9 (4K)</li>
|
<li><strong>Medium:</strong> 6 (1080p), 7 (2K), 9 (4K)</li>
|
||||||
<li><strong>Compact:</strong> 7 (1080p), 8 (2K), 10 (4K)</li>
|
<li><strong>Compact:</strong> 7 (1080p), 8 (2K), 10 (4K)</li>
|
||||||
@@ -259,7 +358,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-help">
|
<div class="input-help">
|
||||||
Choose when to display model information and action buttons:
|
Choose when to display model information and action buttons:
|
||||||
<ul>
|
<ul class="list-description">
|
||||||
<li><strong>Always Visible:</strong> Headers and footers are always visible</li>
|
<li><strong>Always Visible:</strong> Headers and footers are always visible</li>
|
||||||
<li><strong>Reveal on Hover:</strong> Headers and footers only appear when hovering over a card</li>
|
<li><strong>Reveal on Hover:</strong> Headers and footers only appear when hovering over a card</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -267,29 +366,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Cache Management Section -->
|
|
||||||
<!-- <div class="settings-section">
|
|
||||||
<h3>Cache Management</h3>
|
|
||||||
|
|
||||||
<div class="setting-item">
|
|
||||||
<div class="setting-row">
|
|
||||||
<div class="setting-info">
|
|
||||||
<label>Clear Cache Files</label>
|
|
||||||
</div>
|
|
||||||
<div class="setting-control">
|
|
||||||
<button id="clearCacheBtn" class="secondary-btn" onclick="settingsManager.confirmClearCache()">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
<span>Clear Cache</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-help">
|
|
||||||
Removes cached model data. System will rebuild cache on next startup or when triggered manually.
|
|
||||||
<span class="warning-text">May cause temporary performance impact during rebuild.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- Add Example Images Settings Section -->
|
<!-- Add Example Images Settings Section -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Example Images</h3>
|
<h3>Example Images</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user