mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32f42bafaa | ||
|
|
4081b7f022 | ||
|
|
a5808193a6 | ||
|
|
854ca322c1 | ||
|
|
c1d9b5137a | ||
|
|
f33d5745b3 |
@@ -119,10 +119,10 @@ class RecipeMetadataParser(ABC):
|
|||||||
# Check if exists locally
|
# Check if exists locally
|
||||||
if recipe_scanner and lora_entry['hash']:
|
if recipe_scanner and lora_entry['hash']:
|
||||||
lora_scanner = recipe_scanner._lora_scanner
|
lora_scanner = recipe_scanner._lora_scanner
|
||||||
exists_locally = lora_scanner.has_lora_hash(lora_entry['hash'])
|
exists_locally = lora_scanner.has_hash(lora_entry['hash'])
|
||||||
if exists_locally:
|
if exists_locally:
|
||||||
try:
|
try:
|
||||||
local_path = lora_scanner.get_lora_path_by_hash(lora_entry['hash'])
|
local_path = lora_scanner.get_path_by_hash(lora_entry['hash'])
|
||||||
lora_entry['existsLocally'] = True
|
lora_entry['existsLocally'] = True
|
||||||
lora_entry['localPath'] = local_path
|
lora_entry['localPath'] = local_path
|
||||||
lora_entry['file_name'] = os.path.splitext(os.path.basename(local_path))[0]
|
lora_entry['file_name'] = os.path.splitext(os.path.basename(local_path))[0]
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class RecipeFormatParser(RecipeMetadataParser):
|
|||||||
# Check if this LoRA exists locally by SHA256 hash
|
# Check if this LoRA exists locally by SHA256 hash
|
||||||
if lora.get('hash') and recipe_scanner:
|
if lora.get('hash') and recipe_scanner:
|
||||||
lora_scanner = recipe_scanner._lora_scanner
|
lora_scanner = recipe_scanner._lora_scanner
|
||||||
exists_locally = lora_scanner.has_lora_hash(lora['hash'])
|
exists_locally = lora_scanner.has_hash(lora['hash'])
|
||||||
if exists_locally:
|
if exists_locally:
|
||||||
lora_cache = await lora_scanner.get_cached_data()
|
lora_cache = await lora_scanner.get_cached_data()
|
||||||
lora_item = next((item for item in lora_cache.raw_data if item['sha256'].lower() == lora['hash'].lower()), None)
|
lora_item = next((item for item in lora_cache.raw_data if item['sha256'].lower() == lora['hash'].lower()), None)
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ class BaseModelRoutes(ABC):
|
|||||||
group["models"].append(await self.service.format_response(model))
|
group["models"].append(await self.service.format_response(model))
|
||||||
|
|
||||||
# Find the model from the main index too
|
# Find the model from the main index too
|
||||||
hash_val = self.service.scanner._hash_index.get_hash_by_filename(filename)
|
hash_val = self.service.scanner.get_hash_by_filename(filename)
|
||||||
if hash_val:
|
if hash_val:
|
||||||
main_path = self.service.get_path_by_hash(hash_val)
|
main_path = self.service.get_path_by_hash(hash_val)
|
||||||
if main_path and main_path not in paths:
|
if main_path and main_path not in paths:
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ class MiscRoutes:
|
|||||||
|
|
||||||
# Validate and update settings
|
# Validate and update settings
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
|
if value == settings.get(key):
|
||||||
|
# No change, skip
|
||||||
|
continue
|
||||||
# Special handling for example_images_path - verify path exists
|
# Special handling for example_images_path - verify path exists
|
||||||
if key == 'example_images_path' and value:
|
if key == 'example_images_path' and value:
|
||||||
if not os.path.exists(value):
|
if not os.path.exists(value):
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ class UpdateRoutes:
|
|||||||
|
|
||||||
git_info = {
|
git_info = {
|
||||||
'commit_hash': 'unknown',
|
'commit_hash': 'unknown',
|
||||||
'short_hash': 'unknown',
|
'short_hash': 'stable',
|
||||||
'branch': 'unknown',
|
'branch': 'unknown',
|
||||||
'commit_date': 'unknown'
|
'commit_date': 'unknown'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-lora-manager"
|
name = "comfyui-lora-manager"
|
||||||
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
||||||
version = "0.8.22"
|
version = "0.8.23"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp",
|
"aiohttp",
|
||||||
|
|||||||
@@ -424,6 +424,33 @@
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Style for version name */
|
||||||
|
.version-name {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgba(255,255,255,0.8); /* Muted white */
|
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||||
|
font-size: 0.85em;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-top: 2px;
|
||||||
|
opacity: 0.8; /* Slightly transparent for better readability */
|
||||||
|
border: 1px solid rgba(255,255,255,0.25); /* Subtle border */
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
padding: 1px 6px;
|
||||||
|
background: rgba(0,0,0,0.18); /* Optional: subtle background for contrast */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Medium density adjustments for version name */
|
||||||
|
.medium-density .version-name {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact density adjustments for version name */
|
||||||
|
.compact-density .version-name {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Prevent text selection on cards and interactive elements */
|
/* Prevent text selection on cards and interactive elements */
|
||||||
.model-card,
|
.model-card,
|
||||||
.model-card *,
|
.model-card *,
|
||||||
|
|||||||
@@ -183,7 +183,11 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-file-name-btn {
|
/* 合并编辑按钮样式 */
|
||||||
|
.edit-model-name-btn,
|
||||||
|
.edit-file-name-btn,
|
||||||
|
.edit-base-model-btn,
|
||||||
|
.edit-model-description-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
@@ -195,17 +199,28 @@
|
|||||||
margin-left: var(--space-1);
|
margin-left: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-model-name-btn.visible,
|
||||||
.edit-file-name-btn.visible,
|
.edit-file-name-btn.visible,
|
||||||
.file-name-wrapper:hover .edit-file-name-btn {
|
.edit-base-model-btn.visible,
|
||||||
|
.edit-model-description-btn.visible,
|
||||||
|
.model-name-header:hover .edit-model-name-btn,
|
||||||
|
.file-name-wrapper:hover .edit-file-name-btn,
|
||||||
|
.base-model-display:hover .edit-base-model-btn,
|
||||||
|
.model-name-header:hover .edit-model-description-btn {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-file-name-btn:hover {
|
.edit-model-name-btn:hover,
|
||||||
|
.edit-file-name-btn:hover,
|
||||||
|
.edit-base-model-btn:hover,
|
||||||
|
.edit-model-description-btn:hover {
|
||||||
opacity: 0.8 !important;
|
opacity: 0.8 !important;
|
||||||
background: rgba(0, 0, 0, 0.05);
|
background: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .edit-file-name-btn:hover {
|
[data-theme="dark"] .edit-model-name-btn:hover,
|
||||||
|
[data-theme="dark"] .edit-file-name-btn:hover,
|
||||||
|
[data-theme="dark"] .edit-base-model-btn:hover {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,32 +249,6 @@
|
|||||||
flex: 1;
|
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 {
|
.base-model-selector {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
@@ -316,32 +305,6 @@
|
|||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-model-name-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-model-name-btn.visible,
|
|
||||||
.model-name-header:hover .edit-model-name-btn {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-model-name-btn:hover {
|
|
||||||
opacity: 0.8 !important;
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .edit-model-name-btn:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab System Styling */
|
/* Tab System Styling */
|
||||||
.showcase-tabs {
|
.showcase-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ export function createModelCard(model, modelType) {
|
|||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="model-info">
|
<div class="model-info">
|
||||||
<span class="model-name">${model.model_name}</span>
|
<span class="model-name">${model.model_name}</span>
|
||||||
|
${model.civitai?.name ? `<span class="version-name">${model.civitai.name}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<i class="fas fa-folder-open"
|
<i class="fas fa-folder-open"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModelDescription.js
|
* ModelDescription.js
|
||||||
* Handles model description related functionality - General version
|
* Handles model description related functionality - General version
|
||||||
@@ -41,3 +43,98 @@ export function setupTabSwitching() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up model description editing functionality
|
||||||
|
* @param {string} filePath - File path
|
||||||
|
*/
|
||||||
|
export function setupModelDescriptionEditing(filePath) {
|
||||||
|
const descContent = document.querySelector('.model-description-content');
|
||||||
|
const descContainer = document.querySelector('.model-description-container');
|
||||||
|
if (!descContent || !descContainer) return;
|
||||||
|
|
||||||
|
// Add edit button if not present
|
||||||
|
let editBtn = descContainer.querySelector('.edit-model-description-btn');
|
||||||
|
if (!editBtn) {
|
||||||
|
editBtn = document.createElement('button');
|
||||||
|
editBtn.className = 'edit-model-description-btn';
|
||||||
|
editBtn.title = 'Edit model description';
|
||||||
|
editBtn.innerHTML = '<i class="fas fa-pencil-alt"></i>';
|
||||||
|
descContainer.insertBefore(editBtn, descContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show edit button on hover
|
||||||
|
descContainer.addEventListener('mouseenter', () => {
|
||||||
|
editBtn.classList.add('visible');
|
||||||
|
});
|
||||||
|
descContainer.addEventListener('mouseleave', () => {
|
||||||
|
if (!descContainer.classList.contains('editing')) {
|
||||||
|
editBtn.classList.remove('visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle edit button click
|
||||||
|
editBtn.addEventListener('click', () => {
|
||||||
|
descContainer.classList.add('editing');
|
||||||
|
descContent.setAttribute('contenteditable', 'true');
|
||||||
|
descContent.dataset.originalValue = descContent.innerHTML.trim();
|
||||||
|
descContent.focus();
|
||||||
|
|
||||||
|
// Place cursor at the end
|
||||||
|
const range = document.createRange();
|
||||||
|
const sel = window.getSelection();
|
||||||
|
range.selectNodeContents(descContent);
|
||||||
|
range.collapse(false);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
editBtn.classList.add('visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard events
|
||||||
|
descContent.addEventListener('keydown', function(e) {
|
||||||
|
if (!this.getAttribute('contenteditable')) return;
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.blur();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.innerHTML = this.dataset.originalValue;
|
||||||
|
exitEditMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save on blur
|
||||||
|
descContent.addEventListener('blur', async function() {
|
||||||
|
if (!this.getAttribute('contenteditable')) return;
|
||||||
|
const newValue = this.innerHTML.trim();
|
||||||
|
const originalValue = this.dataset.originalValue;
|
||||||
|
if (newValue === originalValue) {
|
||||||
|
exitEditMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newValue) {
|
||||||
|
this.innerHTML = originalValue;
|
||||||
|
showToast('Description cannot be empty', 'error');
|
||||||
|
exitEditMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Save to backend
|
||||||
|
const { getModelApiClient } = await import('../../api/baseModelApi.js');
|
||||||
|
await getModelApiClient().saveModelMetadata(filePath, { modelDescription: newValue });
|
||||||
|
showToast('Model description updated', 'success');
|
||||||
|
} catch (err) {
|
||||||
|
this.innerHTML = originalValue;
|
||||||
|
showToast('Failed to update model description', 'error');
|
||||||
|
} finally {
|
||||||
|
exitEditMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function exitEditMode() {
|
||||||
|
descContent.removeAttribute('contenteditable');
|
||||||
|
descContainer.classList.remove('editing');
|
||||||
|
editBtn.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
scrollToTop,
|
scrollToTop,
|
||||||
loadExampleImages
|
loadExampleImages
|
||||||
} from './showcase/ShowcaseView.js';
|
} from './showcase/ShowcaseView.js';
|
||||||
import { setupTabSwitching } from './ModelDescription.js';
|
import { setupTabSwitching, setupModelDescriptionEditing } from './ModelDescription.js';
|
||||||
import {
|
import {
|
||||||
setupModelNameEditing,
|
setupModelNameEditing,
|
||||||
setupBaseModelEditing,
|
setupBaseModelEditing,
|
||||||
@@ -33,7 +33,6 @@ export function showModelModal(model, modelType) {
|
|||||||
model.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : [];
|
model.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : [];
|
||||||
|
|
||||||
// Generate model type specific content
|
// Generate model type specific content
|
||||||
// const typeSpecificContent = modelType === 'loras' ? renderLoraSpecificContent(model, escapedWords) : '';
|
|
||||||
let typeSpecificContent;
|
let typeSpecificContent;
|
||||||
if (modelType === 'loras') {
|
if (modelType === 'loras') {
|
||||||
typeSpecificContent = renderLoraSpecificContent(model, escapedWords);
|
typeSpecificContent = renderLoraSpecificContent(model, escapedWords);
|
||||||
@@ -211,6 +210,7 @@ export function showModelModal(model, modelType) {
|
|||||||
setupModelNameEditing(model.file_path);
|
setupModelNameEditing(model.file_path);
|
||||||
setupBaseModelEditing(model.file_path);
|
setupBaseModelEditing(model.file_path);
|
||||||
setupFileNameEditing(model.file_path);
|
setupFileNameEditing(model.file_path);
|
||||||
|
setupModelDescriptionEditing(model.file_path, model.modelDescription || '');
|
||||||
setupEventHandlers(model.file_path);
|
setupEventHandlers(model.file_path);
|
||||||
|
|
||||||
// LoRA specific setup
|
// LoRA specific setup
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { modalManager } from './ModalManager.js';
|
import { modalManager } from './ModalManager.js';
|
||||||
import { showToast } from '../utils/uiHelpers.js';
|
|
||||||
import { LoadingManager } from './LoadingManager.js';
|
import { LoadingManager } from './LoadingManager.js';
|
||||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
|
||||||
import { ImportStepManager } from './import/ImportStepManager.js';
|
import { ImportStepManager } from './import/ImportStepManager.js';
|
||||||
import { ImageProcessor } from './import/ImageProcessor.js';
|
import { ImageProcessor } from './import/ImageProcessor.js';
|
||||||
import { RecipeDataManager } from './import/RecipeDataManager.js';
|
import { RecipeDataManager } from './import/RecipeDataManager.js';
|
||||||
@@ -86,8 +84,8 @@ export class ImportManager {
|
|||||||
const uploadError = document.getElementById('uploadError');
|
const uploadError = document.getElementById('uploadError');
|
||||||
if (uploadError) uploadError.textContent = '';
|
if (uploadError) uploadError.textContent = '';
|
||||||
|
|
||||||
const urlError = document.getElementById('urlError');
|
const importUrlError = document.getElementById('importUrlError');
|
||||||
if (urlError) urlError.textContent = '';
|
if (importUrlError) importUrlError.textContent = '';
|
||||||
|
|
||||||
const recipeName = document.getElementById('recipeName');
|
const recipeName = document.getElementById('recipeName');
|
||||||
if (recipeName) recipeName.value = '';
|
if (recipeName) recipeName.value = '';
|
||||||
@@ -167,10 +165,10 @@ export class ImportManager {
|
|||||||
|
|
||||||
// Clear error messages
|
// Clear error messages
|
||||||
const uploadError = document.getElementById('uploadError');
|
const uploadError = document.getElementById('uploadError');
|
||||||
const urlError = document.getElementById('urlError');
|
const importUrlError = document.getElementById('importUrlError');
|
||||||
|
|
||||||
if (uploadError) uploadError.textContent = '';
|
if (uploadError) uploadError.textContent = '';
|
||||||
if (urlError) urlError.textContent = '';
|
if (importUrlError) importUrlError.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImageUpload(event) {
|
handleImageUpload(event) {
|
||||||
@@ -224,8 +222,8 @@ export class ImportManager {
|
|||||||
const uploadError = document.getElementById('uploadError');
|
const uploadError = document.getElementById('uploadError');
|
||||||
if (uploadError) uploadError.textContent = '';
|
if (uploadError) uploadError.textContent = '';
|
||||||
|
|
||||||
const urlError = document.getElementById('urlError');
|
const importUrlError = document.getElementById('importUrlError');
|
||||||
if (urlError) urlError.textContent = '';
|
if (importUrlError) importUrlError.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
backToDetails() {
|
backToDetails() {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class MoveManager {
|
|||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
// Set default lora root if available
|
// Set default lora root if available
|
||||||
const defaultRoot = getStorageItem('settings', {}).default_loras_root;
|
const defaultRoot = getStorageItem('settings', {}).default_lora_root;
|
||||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||||
this.loraRootSelect.value = defaultRoot;
|
this.loraRootSelect.value = defaultRoot;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export class SettingsManager {
|
|||||||
// Ensure settings are loaded from localStorage
|
// Ensure settings are loaded from localStorage
|
||||||
this.loadSettingsFromStorage();
|
this.loadSettingsFromStorage();
|
||||||
|
|
||||||
|
// Sync settings to backend if needed
|
||||||
|
this.syncSettingsToBackendIfNeeded();
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +26,13 @@ export class SettingsManager {
|
|||||||
// Get saved settings from localStorage
|
// Get saved settings from localStorage
|
||||||
const savedSettings = getStorageItem('settings');
|
const savedSettings = getStorageItem('settings');
|
||||||
|
|
||||||
|
// Migrate legacy default_loras_root to default_lora_root if present
|
||||||
|
if (savedSettings && savedSettings.default_loras_root && !savedSettings.default_lora_root) {
|
||||||
|
savedSettings.default_lora_root = savedSettings.default_loras_root;
|
||||||
|
delete savedSettings.default_loras_root;
|
||||||
|
setStorageItem('settings', savedSettings);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply saved settings to state if available
|
// Apply saved settings to state if available
|
||||||
if (savedSettings) {
|
if (savedSettings) {
|
||||||
state.global.settings = { ...state.global.settings, ...savedSettings };
|
state.global.settings = { ...state.global.settings, ...savedSettings };
|
||||||
@@ -74,6 +84,50 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncSettingsToBackendIfNeeded() {
|
||||||
|
// Get local settings from storage
|
||||||
|
const localSettings = getStorageItem('settings') || {};
|
||||||
|
|
||||||
|
// Fields that need to be synced to backend
|
||||||
|
const fieldsToSync = [
|
||||||
|
'civitai_api_key',
|
||||||
|
'default_lora_root',
|
||||||
|
'default_checkpoint_root',
|
||||||
|
'default_embedding_root',
|
||||||
|
'base_model_path_mappings',
|
||||||
|
'download_path_template'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build payload for syncing
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
fieldsToSync.forEach(key => {
|
||||||
|
if (localSettings[key] !== undefined) {
|
||||||
|
if (key === 'base_model_path_mappings') {
|
||||||
|
payload[key] = JSON.stringify(localSettings[key]);
|
||||||
|
} else {
|
||||||
|
payload[key] = localSettings[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only send request if there is something to sync
|
||||||
|
if (Object.keys(payload).length > 0) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
// Log success to console
|
||||||
|
console.log('Settings synced to backend');
|
||||||
|
} catch (e) {
|
||||||
|
// Log error to console
|
||||||
|
console.error('Failed to sync settings to backend:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
if (this.initialized) return;
|
if (this.initialized) return;
|
||||||
|
|
||||||
@@ -159,8 +213,6 @@ export class SettingsManager {
|
|||||||
|
|
||||||
// Load default embedding root
|
// Load default embedding root
|
||||||
await this.loadEmbeddingRoots();
|
await this.loadEmbeddingRoots();
|
||||||
|
|
||||||
// Backend settings are loaded from the template directly
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadLoraRoots() {
|
async loadLoraRoots() {
|
||||||
@@ -193,7 +245,7 @@ export class SettingsManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set selected value from settings
|
// Set selected value from settings
|
||||||
const defaultRoot = state.global.settings.default_loras_root || '';
|
const defaultRoot = state.global.settings.default_lora_root || '';
|
||||||
defaultLoraRootSelect.value = defaultRoot;
|
defaultLoraRootSelect.value = defaultRoot;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -507,7 +559,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// For backend settings, make API call
|
// For backend settings, make API call
|
||||||
if (['show_only_sfw', 'blur_mature_content', 'autoplay_on_hover', 'optimize_example_images', 'use_centralized_examples'].includes(settingKey)) {
|
if (['show_only_sfw'].includes(settingKey)) {
|
||||||
const payload = {};
|
const payload = {};
|
||||||
payload[settingKey] = value;
|
payload[settingKey] = value;
|
||||||
|
|
||||||
@@ -552,7 +604,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
// Update frontend state
|
// Update frontend state
|
||||||
if (settingKey === 'default_lora_root') {
|
if (settingKey === 'default_lora_root') {
|
||||||
state.global.settings.default_loras_root = value;
|
state.global.settings.default_lora_root = value;
|
||||||
} else if (settingKey === 'default_checkpoint_root') {
|
} else if (settingKey === 'default_checkpoint_root') {
|
||||||
state.global.settings.default_checkpoint_root = value;
|
state.global.settings.default_checkpoint_root = value;
|
||||||
} else if (settingKey === 'default_embedding_root') {
|
} else if (settingKey === 'default_embedding_root') {
|
||||||
@@ -632,10 +684,7 @@ export class SettingsManager {
|
|||||||
// Update state
|
// Update state
|
||||||
state.global.settings[settingKey] = value;
|
state.global.settings[settingKey] = value;
|
||||||
|
|
||||||
// Save to localStorage if appropriate
|
setStorageItem('settings', state.global.settings);
|
||||||
if (!settingKey.includes('api_key')) { // Don't store API keys in localStorage for security
|
|
||||||
setStorageItem('settings', state.global.settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For backend settings, make API call
|
// For backend settings, make API call
|
||||||
const payload = {};
|
const payload = {};
|
||||||
@@ -717,69 +766,6 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSettings() {
|
|
||||||
// Get frontend settings from UI
|
|
||||||
const blurMatureContent = document.getElementById('blurMatureContent').checked;
|
|
||||||
const showOnlySFW = document.getElementById('showOnlySFW').checked;
|
|
||||||
const defaultLoraRoot = document.getElementById('defaultLoraRoot').value;
|
|
||||||
const defaultCheckpointRoot = document.getElementById('defaultCheckpointRoot').value;
|
|
||||||
const autoplayOnHover = document.getElementById('autoplayOnHover').checked;
|
|
||||||
const optimizeExampleImages = document.getElementById('optimizeExampleImages').checked;
|
|
||||||
|
|
||||||
// Get backend settings
|
|
||||||
const apiKey = document.getElementById('civitaiApiKey').value;
|
|
||||||
|
|
||||||
// Update frontend state and save to localStorage
|
|
||||||
state.global.settings.blurMatureContent = blurMatureContent;
|
|
||||||
state.global.settings.show_only_sfw = showOnlySFW;
|
|
||||||
state.global.settings.default_loras_root = defaultLoraRoot;
|
|
||||||
state.global.settings.default_checkpoint_root = defaultCheckpointRoot;
|
|
||||||
state.global.settings.autoplayOnHover = autoplayOnHover;
|
|
||||||
state.global.settings.optimizeExampleImages = optimizeExampleImages;
|
|
||||||
|
|
||||||
// Save settings to localStorage
|
|
||||||
setStorageItem('settings', state.global.settings);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Save backend settings via API
|
|
||||||
const response = await fetch('/api/settings', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
civitai_api_key: apiKey,
|
|
||||||
show_only_sfw: showOnlySFW,
|
|
||||||
optimize_example_images: optimizeExampleImages,
|
|
||||||
default_checkpoint_root: defaultCheckpointRoot
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to save settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast('Settings saved successfully', 'success');
|
|
||||||
modalManager.closeModal('settingsModal');
|
|
||||||
|
|
||||||
// Apply frontend settings immediately
|
|
||||||
this.applyFrontendSettings();
|
|
||||||
|
|
||||||
if (this.currentPage === 'loras') {
|
|
||||||
// Reload the loras without updating folders
|
|
||||||
await resetAndReload(false);
|
|
||||||
} else if (this.currentPage === 'recipes') {
|
|
||||||
// Reload the recipes without updating folders
|
|
||||||
await window.recipeManager.loadRecipes();
|
|
||||||
} else if (this.currentPage === 'checkpoints') {
|
|
||||||
// Reload the checkpoints without updating folders
|
|
||||||
await window.checkpointsManager.loadCheckpoints();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showToast('Failed to save settings: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFrontendSettings() {
|
applyFrontendSettings() {
|
||||||
// Apply blur setting to existing content
|
// Apply blur setting to existing content
|
||||||
const blurSetting = state.global.settings.blurMatureContent;
|
const blurSetting = state.global.settings.blurMatureContent;
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export class FolderBrowser {
|
|||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
// Set default lora root if available
|
// Set default lora root if available
|
||||||
const defaultRoot = getStorageItem('settings', {}).default_loras_root;
|
const defaultRoot = getStorageItem('settings', {}).default_lora_root;
|
||||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||||
loraRoot.value = defaultRoot;
|
loraRoot.value = defaultRoot;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class ImageProcessor {
|
|||||||
|
|
||||||
async handleUrlInput() {
|
async handleUrlInput() {
|
||||||
const urlInput = document.getElementById('imageUrlInput');
|
const urlInput = document.getElementById('imageUrlInput');
|
||||||
const errorElement = document.getElementById('urlError');
|
const errorElement = document.getElementById('importUrlError');
|
||||||
const input = urlInput.value.trim();
|
const input = urlInput.value.trim();
|
||||||
|
|
||||||
// Validate input
|
// Validate input
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<i class="fas fa-download"></i> Fetch Image
|
<i class="fas fa-download"></i> Fetch Image
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="error-message" id="urlError"></div>
|
<div class="error-message" id="importUrlError"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user