/** * LoraModal - 主入口点 * * 将原始的LoraModal.js拆分成多个功能模块后的主入口文件 */ import { showToast, copyToClipboard } from '../../utils/uiHelpers.js'; import { modalManager } from '../../managers/ModalManager.js'; import { renderShowcaseContent, toggleShowcase, setupShowcaseScroll, scrollToTop, initExampleImport } from './ShowcaseView.js'; import { setupTabSwitching, loadModelDescription } from './ModelDescription.js'; import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js'; import { parsePresets, renderPresetTags } from './PresetTags.js'; import { loadRecipesForLora } from './RecipeTab.js'; import { setupTagEditMode } from './ModelTags.js'; // Add import for tag editing import { setupModelNameEditing, setupBaseModelEditing, setupFileNameEditing } from './ModelMetadata.js'; import { saveModelMetadata } from '../../api/loraApi.js'; import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js'; /** * 显示LoRA模型弹窗 * @param {Object} lora - LoRA模型数据 */ export function showLoraModal(lora) { const escapedWords = lora.civitai?.trainedWords?.length ? lora.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : []; const content = ` `; modalManager.showModal('loraModal', content); setupEditableFields(lora.file_path); setupShowcaseScroll(); setupTabSwitching(); setupTagTooltip(); setupTriggerWordsEditMode(); setupModelNameEditing(lora.file_path); setupBaseModelEditing(lora.file_path); setupFileNameEditing(lora.file_path); setupTagEditMode(); // Initialize tag editing functionality // If we have a model ID but no description, fetch it if (lora.civitai?.modelId && !lora.modelDescription) { loadModelDescription(lora.civitai.modelId, lora.file_path); } // Load recipes for this Lora loadRecipesForLora(lora.model_name, lora.sha256); // Load example images asynchronously - merge regular and custom images const regularImages = lora.civitai?.images || []; const customImages = lora.civitai?.customImages || []; // Combine images - regular images first, then custom images const allImages = [...regularImages, ...customImages]; loadExampleImages(allImages, lora.sha256); } /** * Load example images asynchronously * @param {Array} images - Array of image objects (both regular and custom) * @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) { // Only initialize if we actually have examples and they're expanded if (!carousel.classList.contains('collapsed')) { initLazyLoading(carousel); initNsfwBlurHandlers(carousel); initMetadataPanelHandlers(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) { const content = document.querySelector('.notes-content').textContent; try { await saveModelMetadata(filePath, { notes: content }); showToast('Notes saved successfully', 'success'); } catch (error) { showToast('Failed to save notes', 'error'); } }; function setupEditableFields(filePath) { const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); editableFields.forEach(field => { field.addEventListener('focus', function() { if (this.textContent === 'Add your notes here...') { this.textContent = ''; } }); field.addEventListener('blur', function() { if (this.textContent.trim() === '') { if (this.classList.contains('notes-content')) { this.textContent = 'Add your notes here...'; } } }); }); const presetSelector = document.getElementById('preset-selector'); const presetValue = document.getElementById('preset-value'); const addPresetBtn = document.querySelector('.add-preset-btn'); const presetTags = document.querySelector('.preset-tags'); presetSelector.addEventListener('change', function() { const selected = this.value; if (selected) { presetValue.style.display = 'inline-block'; presetValue.min = selected.includes('strength') ? -10 : 0; presetValue.max = selected.includes('strength') ? 10 : 10; presetValue.step = 0.5; if (selected === 'clip_skip') { presetValue.type = 'number'; presetValue.step = 1; } // Add auto-focus setTimeout(() => presetValue.focus(), 0); } else { presetValue.style.display = 'none'; } }); addPresetBtn.addEventListener('click', async function() { const key = presetSelector.value; const value = presetValue.value; if (!key || !value) return; const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`); const currentPresets = parsePresets(loraCard.dataset.usage_tips); currentPresets[key] = parseFloat(value); const newPresetsJson = JSON.stringify(currentPresets); await saveModelMetadata(filePath, { usage_tips: newPresetsJson }); presetTags.innerHTML = renderPresetTags(currentPresets); presetSelector.value = ''; presetValue.value = ''; presetValue.style.display = 'none'; }); // Add keydown event listeners for notes const notesContent = document.querySelector('.notes-content'); if (notesContent) { notesContent.addEventListener('keydown', async function(e) { if (e.key === 'Enter') { if (e.shiftKey) { // Allow shift+enter for new line return; } e.preventDefault(); await saveNotes(filePath); } }); } // Add keydown event for preset value presetValue.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); addPresetBtn.click(); } }); } // Export functions for global access export { toggleShowcase, scrollToTop };