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:
"""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

View File

@@ -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) {
</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">
<button class="tab-btn active" data-tab="showcase">Examples</button>
<button class="tab-btn" data-tab="description">Model Description</button>
@@ -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 = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Error loading example images
</div>
`;
}
}
// 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);
}
/**

View File

@@ -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) {
<label>Additional Notes</label>
<div class="editable-field">
<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>
</button>
</div>
@@ -166,7 +163,7 @@ export function showLoraModal(lora) {
</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>
</button>
</div>
@@ -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 = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Error loading example images
</div>
`;
}
}
});
}
// 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;
}

View File

@@ -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 = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Error loading example images
</div>
`;
}
}
}
/**
* Render showcase content
* @param {Array} images - Array of images/videos to show