refactor: Improve metadata handling and streamline example image loading in modals

This commit is contained in:
Will Miao
2025-06-19 17:07:28 +08:00
parent 605a06317b
commit 8f4d575ec8
4 changed files with 96 additions and 131 deletions

View File

@@ -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

View File

@@ -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>
`;
}
}
} }
/** /**

View File

@@ -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;
// First fetch local example files // Use event delegation to handle clicks
let localFiles = []; modalElement.addEventListener('click', async (event) => {
const target = event.target.closest('[data-action]');
if (!target) return;
try { const action = target.dataset.action;
const endpoint = '/api/example-image-files';
const params = `model_hash=${modelHash}`;
const response = await fetch(`${endpoint}?${params}`); switch (action) {
const result = await response.json(); case 'close-modal':
modalManager.closeModal('loraModal');
break;
if (result.success) { case 'save-notes':
localFiles = result.files; await saveNotes(filePath);
} break;
} catch (error) {
console.error("Failed to get example files:", error); 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 = `
<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 });
@@ -363,5 +328,3 @@ function setupEditableFields(filePath) {
} }
}); });
} }
window.scrollToTop = scrollToTop;

View File

@@ -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