From 8f4d575ec8accc8224d48e2b45f3acb940191c16 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 19 Jun 2025 17:07:28 +0800 Subject: [PATCH] refactor: Improve metadata handling and streamline example image loading in modals --- py/utils/routes_common.py | 8 +- static/js/components/checkpointModal/index.js | 68 ++----------- static/js/components/loraModal/index.js | 99 ++++++------------- .../shared/showcase/ShowcaseView.js | 52 ++++++++++ 4 files changed, 96 insertions(+), 131 deletions(-) diff --git a/py/utils/routes_common.py b/py/utils/routes_common.py index 88068459..f337488e 100644 --- a/py/utils/routes_common.py +++ b/py/utils/routes_common.py @@ -40,16 +40,16 @@ class ModelRouteUtils: civitai_metadata: Dict, client: CivitaiClient) -> None: """Update local metadata with CivitAI data""" # Save existing trainedWords and customImages if they exist - existing_civitai = local_metadata.get('civitai', {}) - existing_trained_words = existing_civitai.get('trainedWords', []) + existing_civitai = local_metadata.get('civitai') or {} # Use empty dict if None # Create a new civitai metadata by updating existing with new merged_civitai = existing_civitai.copy() merged_civitai.update(civitai_metadata) # Special handling for trainedWords - ensure we don't lose any existing trained words - new_trained_words = civitai_metadata.get('trainedWords', []) - if existing_trained_words: + if 'trainedWords' in existing_civitai: + 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 merged_trained_words = list(set(existing_trained_words + new_trained_words)) merged_civitai['trainedWords'] = merged_trained_words diff --git a/static/js/components/checkpointModal/index.js b/static/js/components/checkpointModal/index.js index cdd61b91..56c0f03e 100644 --- a/static/js/components/checkpointModal/index.js +++ b/static/js/components/checkpointModal/index.js @@ -6,12 +6,10 @@ import { showToast } from '../../utils/uiHelpers.js'; import { modalManager } from '../../managers/ModalManager.js'; import { - renderShowcaseContent, - initShowcaseContent, toggleShowcase, setupShowcaseScroll, scrollToTop, - initExampleImport + loadExampleImages } from '../shared/showcase/ShowcaseView.js'; import { setupTabSwitching, loadModelDescription } from './ModelDescription.js'; import { @@ -108,7 +106,7 @@ export function showCheckpointModal(checkpoint) { -
+
@@ -143,7 +141,7 @@ export function showCheckpointModal(checkpoint) { modalManager.showModal('checkpointModal', content); setupEditableFields(checkpoint.file_path); - setupShowcaseScroll(); + setupShowcaseScroll('checkpointModal'); setupTabSwitching(); setupTagTooltip(); setupTagEditMode(); // Initialize tag editing functionality @@ -156,60 +154,12 @@ export function showCheckpointModal(checkpoint) { loadModelDescription(checkpoint.civitai.modelId, checkpoint.file_path); } - // Load example images asynchronously - loadExampleImages(checkpoint.civitai?.images, checkpoint.sha256, checkpoint.file_path); -} - -/** - * Load example images asynchronously - * @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 = ` -
- - Error loading example images -
- `; - } - } + // Load example images asynchronously - merge regular and custom images + const regularImages = checkpoint.civitai?.images || []; + const customImages = checkpoint.civitai?.customImages || []; + // Combine images - regular images first, then custom images + const allImages = [...regularImages, ...customImages]; + loadExampleImages(allImages, checkpoint.sha256); } /** diff --git a/static/js/components/loraModal/index.js b/static/js/components/loraModal/index.js index e57c121d..f78e7be3 100644 --- a/static/js/components/loraModal/index.js +++ b/static/js/components/loraModal/index.js @@ -3,15 +3,12 @@ * * 将原始的LoraModal.js拆分成多个功能模块后的主入口文件 */ -import { showToast, copyToClipboard } from '../../utils/uiHelpers.js'; +import { showToast } from '../../utils/uiHelpers.js'; import { modalManager } from '../../managers/ModalManager.js'; import { - renderShowcaseContent, - initShowcaseContent, - toggleShowcase, setupShowcaseScroll, scrollToTop, - initExampleImport + loadExampleImages } from '../shared/showcase/ShowcaseView.js'; import { setupTabSwitching, loadModelDescription } from './ModelDescription.js'; import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js'; @@ -122,7 +119,7 @@ export function showLoraModal(lora) {
${lora.notes || 'Add your notes here...'}
-
@@ -166,7 +163,7 @@ export function showLoraModal(lora) {
-
@@ -184,6 +181,7 @@ export function showLoraModal(lora) { setupBaseModelEditing(lora.file_path); setupFileNameEditing(lora.file_path); setupTagEditMode(); // Initialize tag editing functionality + setupEventHandlers(lora.file_path); // If we have a model ID but no description, fetch it if (lora.civitai?.modelId && !lora.modelDescription) { @@ -202,69 +200,36 @@ export function showLoraModal(lora) { } /** - * Load example images asynchronously - * @param {Array} images - Array of image objects (both regular and custom) - * @param {string} modelHash - Model hash for fetching local files + * Sets up event handlers using event delegation + * @param {string} filePath - Path to the model file */ -async function loadExampleImages(images, modelHash) { - try { - const showcaseTab = document.getElementById('showcase-tab'); - if (!showcaseTab) return; +function setupEventHandlers(filePath) { + const modalElement = document.getElementById('loraModal'); + + // 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 - 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); + const action = target.dataset.action; + + switch (action) { + case 'close-modal': + modalManager.closeModal('loraModal'); + break; + + case 'save-notes': + await saveNotes(filePath); + break; + + case 'scroll-to-top': + scrollToTop(target); + break; } - - // 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 = ` -
- - Error loading example images -
- `; - } - } + }); } -// Copy file name function -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) { +async function saveNotes(filePath) { const content = document.querySelector('.notes-content').textContent; try { await saveModelMetadata(filePath, { notes: content }); @@ -362,6 +327,4 @@ function setupEditableFields(filePath) { addPresetBtn.click(); } }); -} - -window.scrollToTop = scrollToTop; \ No newline at end of file +} \ No newline at end of file diff --git a/static/js/components/shared/showcase/ShowcaseView.js b/static/js/components/shared/showcase/ShowcaseView.js index b921588a..d3133b7b 100644 --- a/static/js/components/shared/showcase/ShowcaseView.js +++ b/static/js/components/shared/showcase/ShowcaseView.js @@ -15,6 +15,58 @@ import { import { generateMetadataPanel } from './MetadataPanel.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 = ` +
+ + Error loading example images +
+ `; + } + } +} + /** * Render showcase content * @param {Array} images - Array of images/videos to show