mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
refactor: Improve metadata handling and streamline example image loading in modals
This commit is contained in:
@@ -40,16 +40,16 @@ class ModelRouteUtils:
|
|||||||
civitai_metadata: Dict, client: CivitaiClient) -> None:
|
civitai_metadata: Dict, client: CivitaiClient) -> None:
|
||||||
"""Update local metadata with CivitAI data"""
|
"""Update local metadata with CivitAI data"""
|
||||||
# Save existing trainedWords and customImages if they exist
|
# Save existing trainedWords and customImages if they exist
|
||||||
existing_civitai = local_metadata.get('civitai', {})
|
existing_civitai = local_metadata.get('civitai') or {} # Use empty dict if None
|
||||||
existing_trained_words = existing_civitai.get('trainedWords', [])
|
|
||||||
|
|
||||||
# Create a new civitai metadata by updating existing with new
|
# Create a new civitai metadata by updating existing with new
|
||||||
merged_civitai = existing_civitai.copy()
|
merged_civitai = existing_civitai.copy()
|
||||||
merged_civitai.update(civitai_metadata)
|
merged_civitai.update(civitai_metadata)
|
||||||
|
|
||||||
# Special handling for trainedWords - ensure we don't lose any existing trained words
|
# Special handling for trainedWords - ensure we don't lose any existing trained words
|
||||||
new_trained_words = civitai_metadata.get('trainedWords', [])
|
if 'trainedWords' in existing_civitai:
|
||||||
if existing_trained_words:
|
existing_trained_words = existing_civitai.get('trainedWords', [])
|
||||||
|
new_trained_words = civitai_metadata.get('trainedWords', [])
|
||||||
# Use a set to combine words without duplicates, then convert back to list
|
# Use a set to combine words without duplicates, then convert back to list
|
||||||
merged_trained_words = list(set(existing_trained_words + new_trained_words))
|
merged_trained_words = list(set(existing_trained_words + new_trained_words))
|
||||||
merged_civitai['trainedWords'] = merged_trained_words
|
merged_civitai['trainedWords'] = merged_trained_words
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
import { showToast } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { modalManager } from '../../managers/ModalManager.js';
|
import { modalManager } from '../../managers/ModalManager.js';
|
||||||
import {
|
import {
|
||||||
renderShowcaseContent,
|
|
||||||
initShowcaseContent,
|
|
||||||
toggleShowcase,
|
toggleShowcase,
|
||||||
setupShowcaseScroll,
|
setupShowcaseScroll,
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
initExampleImport
|
loadExampleImages
|
||||||
} from '../shared/showcase/ShowcaseView.js';
|
} from '../shared/showcase/ShowcaseView.js';
|
||||||
import { setupTabSwitching, loadModelDescription } from './ModelDescription.js';
|
import { setupTabSwitching, loadModelDescription } from './ModelDescription.js';
|
||||||
import {
|
import {
|
||||||
@@ -108,7 +106,7 @@ export function showCheckpointModal(checkpoint) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="showcase-section" data-model-hash="${checkpoint.sha256 || ''}">
|
<div class="showcase-section" data-model-hash="${checkpoint.sha256 || ''}" data-filepath="${checkpoint.file_path}">
|
||||||
<div class="showcase-tabs">
|
<div class="showcase-tabs">
|
||||||
<button class="tab-btn active" data-tab="showcase">Examples</button>
|
<button class="tab-btn active" data-tab="showcase">Examples</button>
|
||||||
<button class="tab-btn" data-tab="description">Model Description</button>
|
<button class="tab-btn" data-tab="description">Model Description</button>
|
||||||
@@ -143,7 +141,7 @@ export function showCheckpointModal(checkpoint) {
|
|||||||
|
|
||||||
modalManager.showModal('checkpointModal', content);
|
modalManager.showModal('checkpointModal', content);
|
||||||
setupEditableFields(checkpoint.file_path);
|
setupEditableFields(checkpoint.file_path);
|
||||||
setupShowcaseScroll();
|
setupShowcaseScroll('checkpointModal');
|
||||||
setupTabSwitching();
|
setupTabSwitching();
|
||||||
setupTagTooltip();
|
setupTagTooltip();
|
||||||
setupTagEditMode(); // Initialize tag editing functionality
|
setupTagEditMode(); // Initialize tag editing functionality
|
||||||
@@ -156,60 +154,12 @@ export function showCheckpointModal(checkpoint) {
|
|||||||
loadModelDescription(checkpoint.civitai.modelId, checkpoint.file_path);
|
loadModelDescription(checkpoint.civitai.modelId, checkpoint.file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load example images asynchronously
|
// Load example images asynchronously - merge regular and custom images
|
||||||
loadExampleImages(checkpoint.civitai?.images, checkpoint.sha256, checkpoint.file_path);
|
const regularImages = checkpoint.civitai?.images || [];
|
||||||
}
|
const customImages = checkpoint.civitai?.customImages || [];
|
||||||
|
// Combine images - regular images first, then custom images
|
||||||
/**
|
const allImages = [...regularImages, ...customImages];
|
||||||
* Load example images asynchronously
|
loadExampleImages(allImages, checkpoint.sha256);
|
||||||
* @param {Array} images - Array of image objects
|
|
||||||
* @param {string} modelHash - Model hash for fetching local files
|
|
||||||
*/
|
|
||||||
async function loadExampleImages(images, modelHash) {
|
|
||||||
try {
|
|
||||||
const showcaseTab = document.getElementById('showcase-tab');
|
|
||||||
if (!showcaseTab) return;
|
|
||||||
|
|
||||||
// First fetch local example files
|
|
||||||
let localFiles = [];
|
|
||||||
try {
|
|
||||||
const endpoint = '/api/example-image-files';
|
|
||||||
|
|
||||||
const params = `model_hash=${modelHash}`;
|
|
||||||
|
|
||||||
const response = await fetch(`${endpoint}?${params}`);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
localFiles = result.files;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to get example files:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then render with both remote images and local files
|
|
||||||
showcaseTab.innerHTML = renderShowcaseContent(images, localFiles);
|
|
||||||
|
|
||||||
// Re-initialize the showcase event listeners
|
|
||||||
const carousel = showcaseTab.querySelector('.carousel');
|
|
||||||
if (carousel && !carousel.classList.contains('collapsed')) {
|
|
||||||
initShowcaseContent(carousel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the example import functionality
|
|
||||||
initExampleImport(modelHash, showcaseTab);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading example images:', error);
|
|
||||||
const showcaseTab = document.getElementById('showcase-tab');
|
|
||||||
if (showcaseTab) {
|
|
||||||
showcaseTab.innerHTML = `
|
|
||||||
<div class="error-message">
|
|
||||||
<i class="fas fa-exclamation-circle"></i>
|
|
||||||
Error loading example images
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,15 +3,12 @@
|
|||||||
*
|
*
|
||||||
* 将原始的LoraModal.js拆分成多个功能模块后的主入口文件
|
* 将原始的LoraModal.js拆分成多个功能模块后的主入口文件
|
||||||
*/
|
*/
|
||||||
import { showToast, copyToClipboard } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { modalManager } from '../../managers/ModalManager.js';
|
import { modalManager } from '../../managers/ModalManager.js';
|
||||||
import {
|
import {
|
||||||
renderShowcaseContent,
|
|
||||||
initShowcaseContent,
|
|
||||||
toggleShowcase,
|
|
||||||
setupShowcaseScroll,
|
setupShowcaseScroll,
|
||||||
scrollToTop,
|
scrollToTop,
|
||||||
initExampleImport
|
loadExampleImages
|
||||||
} from '../shared/showcase/ShowcaseView.js';
|
} from '../shared/showcase/ShowcaseView.js';
|
||||||
import { setupTabSwitching, loadModelDescription } from './ModelDescription.js';
|
import { setupTabSwitching, loadModelDescription } from './ModelDescription.js';
|
||||||
import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js';
|
import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js';
|
||||||
@@ -122,7 +119,7 @@ export function showLoraModal(lora) {
|
|||||||
<label>Additional Notes</label>
|
<label>Additional Notes</label>
|
||||||
<div class="editable-field">
|
<div class="editable-field">
|
||||||
<div class="notes-content" contenteditable="true" spellcheck="false">${lora.notes || 'Add your notes here...'}</div>
|
<div class="notes-content" contenteditable="true" spellcheck="false">${lora.notes || 'Add your notes here...'}</div>
|
||||||
<button class="save-btn" onclick="saveNotes('${lora.file_path}')">
|
<button class="save-btn" data-action="save-notes">
|
||||||
<i class="fas fa-save"></i>
|
<i class="fas fa-save"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,7 +163,7 @@ export function showLoraModal(lora) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="back-to-top" onclick="scrollToTop(this)">
|
<button class="back-to-top" data-action="scroll-to-top">
|
||||||
<i class="fas fa-arrow-up"></i>
|
<i class="fas fa-arrow-up"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,6 +181,7 @@ export function showLoraModal(lora) {
|
|||||||
setupBaseModelEditing(lora.file_path);
|
setupBaseModelEditing(lora.file_path);
|
||||||
setupFileNameEditing(lora.file_path);
|
setupFileNameEditing(lora.file_path);
|
||||||
setupTagEditMode(); // Initialize tag editing functionality
|
setupTagEditMode(); // Initialize tag editing functionality
|
||||||
|
setupEventHandlers(lora.file_path);
|
||||||
|
|
||||||
// If we have a model ID but no description, fetch it
|
// If we have a model ID but no description, fetch it
|
||||||
if (lora.civitai?.modelId && !lora.modelDescription) {
|
if (lora.civitai?.modelId && !lora.modelDescription) {
|
||||||
@@ -202,69 +200,36 @@ export function showLoraModal(lora) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load example images asynchronously
|
* Sets up event handlers using event delegation
|
||||||
* @param {Array} images - Array of image objects (both regular and custom)
|
* @param {string} filePath - Path to the model file
|
||||||
* @param {string} modelHash - Model hash for fetching local files
|
|
||||||
*/
|
*/
|
||||||
async function loadExampleImages(images, modelHash) {
|
function setupEventHandlers(filePath) {
|
||||||
try {
|
const modalElement = document.getElementById('loraModal');
|
||||||
const showcaseTab = document.getElementById('showcase-tab');
|
|
||||||
if (!showcaseTab) return;
|
// Use event delegation to handle clicks
|
||||||
|
modalElement.addEventListener('click', async (event) => {
|
||||||
|
const target = event.target.closest('[data-action]');
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
// First fetch local example files
|
const action = target.dataset.action;
|
||||||
let localFiles = [];
|
|
||||||
|
switch (action) {
|
||||||
try {
|
case 'close-modal':
|
||||||
const endpoint = '/api/example-image-files';
|
modalManager.closeModal('loraModal');
|
||||||
const params = `model_hash=${modelHash}`;
|
break;
|
||||||
|
|
||||||
const response = await fetch(`${endpoint}?${params}`);
|
case 'save-notes':
|
||||||
const result = await response.json();
|
await saveNotes(filePath);
|
||||||
|
break;
|
||||||
if (result.success) {
|
|
||||||
localFiles = result.files;
|
case 'scroll-to-top':
|
||||||
}
|
scrollToTop(target);
|
||||||
} catch (error) {
|
break;
|
||||||
console.error("Failed to get example files:", error);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Then render with both remote images and local files
|
|
||||||
showcaseTab.innerHTML = renderShowcaseContent(images, localFiles);
|
|
||||||
|
|
||||||
// Re-initialize the showcase event listeners
|
|
||||||
const carousel = showcaseTab.querySelector('.carousel');
|
|
||||||
if (carousel && !carousel.classList.contains('collapsed')) {
|
|
||||||
initShowcaseContent(carousel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the example import functionality
|
|
||||||
initExampleImport(modelHash, showcaseTab);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading example images:', error);
|
|
||||||
const showcaseTab = document.getElementById('showcase-tab');
|
|
||||||
if (showcaseTab) {
|
|
||||||
showcaseTab.innerHTML = `
|
|
||||||
<div class="error-message">
|
|
||||||
<i class="fas fa-exclamation-circle"></i>
|
|
||||||
Error loading example images
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy file name function
|
async function saveNotes(filePath) {
|
||||||
window.copyFileName = async function(fileName) {
|
|
||||||
try {
|
|
||||||
await copyToClipboard(fileName, 'File name copied');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Copy failed:', err);
|
|
||||||
showToast('Copy failed', 'error');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add save note function
|
|
||||||
window.saveNotes = async function(filePath) {
|
|
||||||
const content = document.querySelector('.notes-content').textContent;
|
const content = document.querySelector('.notes-content').textContent;
|
||||||
try {
|
try {
|
||||||
await saveModelMetadata(filePath, { notes: content });
|
await saveModelMetadata(filePath, { notes: content });
|
||||||
@@ -362,6 +327,4 @@ function setupEditableFields(filePath) {
|
|||||||
addPresetBtn.click();
|
addPresetBtn.click();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.scrollToTop = scrollToTop;
|
|
||||||
@@ -15,6 +15,58 @@ import {
|
|||||||
import { generateMetadataPanel } from './MetadataPanel.js';
|
import { generateMetadataPanel } from './MetadataPanel.js';
|
||||||
import { generateImageWrapper, generateVideoWrapper } from './MediaRenderers.js';
|
import { generateImageWrapper, generateVideoWrapper } from './MediaRenderers.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load example images asynchronously
|
||||||
|
* @param {Array} images - Array of image objects (both regular and custom)
|
||||||
|
* @param {string} modelHash - Model hash for fetching local files
|
||||||
|
*/
|
||||||
|
export async function loadExampleImages(images, modelHash) {
|
||||||
|
try {
|
||||||
|
const showcaseTab = document.getElementById('showcase-tab');
|
||||||
|
if (!showcaseTab) return;
|
||||||
|
|
||||||
|
// First fetch local example files
|
||||||
|
let localFiles = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const endpoint = '/api/example-image-files';
|
||||||
|
const params = `model_hash=${modelHash}`;
|
||||||
|
|
||||||
|
const response = await fetch(`${endpoint}?${params}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
localFiles = result.files;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get example files:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then render with both remote images and local files
|
||||||
|
showcaseTab.innerHTML = renderShowcaseContent(images, localFiles);
|
||||||
|
|
||||||
|
// Re-initialize the showcase event listeners
|
||||||
|
const carousel = showcaseTab.querySelector('.carousel');
|
||||||
|
if (carousel && !carousel.classList.contains('collapsed')) {
|
||||||
|
initShowcaseContent(carousel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the example import functionality
|
||||||
|
initExampleImport(modelHash, showcaseTab);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading example images:', error);
|
||||||
|
const showcaseTab = document.getElementById('showcase-tab');
|
||||||
|
if (showcaseTab) {
|
||||||
|
showcaseTab.innerHTML = `
|
||||||
|
<div class="error-message">
|
||||||
|
<i class="fas fa-exclamation-circle"></i>
|
||||||
|
Error loading example images
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render showcase content
|
* Render showcase content
|
||||||
* @param {Array} images - Array of images/videos to show
|
* @param {Array} images - Array of images/videos to show
|
||||||
|
|||||||
Reference in New Issue
Block a user