From 12d1857b1395f01677162d60efc991f28eb49bbf Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Fri, 25 Jul 2025 22:33:46 +0800 Subject: [PATCH] refactor: update model type references from 'lora' to 'loras' and streamline event delegation setup --- static/js/components/shared/ModelCard.js | 36 ++++------ .../js/components/shared/ModelDescription.js | 61 ----------------- static/js/components/shared/ModelModal.js | 67 +++++-------------- .../shared/showcase/ShowcaseView.js | 12 +++- static/js/utils/infiniteScroll.js | 23 ++----- 5 files changed, 47 insertions(+), 152 deletions(-) diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js index 5e015dc1..6eda1f66 100644 --- a/static/js/components/shared/ModelCard.js +++ b/static/js/components/shared/ModelCard.js @@ -153,7 +153,7 @@ async function toggleFavorite(card) { } function handleSendToWorkflow(card, replaceMode, modelType) { - if (modelType === 'lora') { + if (modelType === 'loras') { const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); const strength = usageTips.strength || 1; const loraSyntax = ``; @@ -165,15 +165,18 @@ function handleSendToWorkflow(card, replaceMode, modelType) { } function handleCopyAction(card, modelType) { - if (modelType === 'lora') { + if (modelType === 'loras') { const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); const strength = usageTips.strength || 1; const loraSyntax = ``; copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard'); - } else { + } else if (modelType === 'checkpoints') { // Checkpoint copy functionality - copy checkpoint name const checkpointName = card.dataset.file_name; copyToClipboard(checkpointName, 'Checkpoint name copied'); + } else if (modelType === 'embeddings') { + const embeddingName = card.dataset.file_name; + copyToClipboard(embeddingName, 'Embedding name copied'); } } @@ -216,7 +219,7 @@ function handleCardClick(card, modelType) { function showModelModalFromCard(card, modelType) { // Get the appropriate preview versions map - const previewVersionsKey = modelType === 'lora' ? 'loras' : 'checkpoints'; + const previewVersionsKey = modelType; const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map(); const version = previewVersions.get(card.dataset.filepath); const previewUrl = card.dataset.preview_url || '/loras_static/images/no-preview.png'; @@ -371,11 +374,11 @@ export function createModelCard(model, modelType) { card.dataset.file_size = model.file_size; card.dataset.from_civitai = model.from_civitai; card.dataset.notes = model.notes || ''; - card.dataset.base_model = model.base_model || (modelType === 'checkpoint' ? 'Unknown' : ''); + card.dataset.base_model = model.base_model || 'Unknown'; card.dataset.favorite = model.favorite ? 'true' : 'false'; // LoRA specific data - if (modelType === 'lora') { + if (modelType === 'loras') { card.dataset.usage_tips = model.usage_tips; } @@ -404,12 +407,12 @@ export function createModelCard(model, modelType) { } // Apply selection state if in bulk mode and this card is in the selected set (LoRA only) - if (modelType === 'lora' && state.bulkMode && state.selectedLoras.has(model.file_path)) { + if (modelType === 'loras' && state.bulkMode && state.selectedLoras.has(model.file_path)) { card.classList.add('selected'); } // Get the appropriate preview versions map - const previewVersionsKey = modelType === 'lora' ? 'loras' : 'checkpoints'; + const previewVersionsKey = modelType; const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map(); const version = previewVersions.get(model.file_path); const previewUrl = model.preview_url || '/loras_static/images/no-preview.png'; @@ -434,8 +437,8 @@ export function createModelCard(model, modelType) { const isFavorite = model.favorite === true; // Generate action icons based on model type - const actionIcons = modelType === 'lora' ? - ` - ` : - ` - - - - - - `; card.innerHTML = ` diff --git a/static/js/components/shared/ModelDescription.js b/static/js/components/shared/ModelDescription.js index 64e78348..a7179805 100644 --- a/static/js/components/shared/ModelDescription.js +++ b/static/js/components/shared/ModelDescription.js @@ -40,65 +40,4 @@ export function setupTabSwitching() { } }); }); -} - -/** - * Load model description - General version supports both LoRA and Checkpoint - * @param {string} modelId - Model ID - * @param {string} filePath - File path - */ -export async function loadModelDescription(modelId, filePath) { - try { - const descriptionContainer = document.querySelector('.model-description-content'); - const loadingElement = document.querySelector('.model-description-loading'); - - if (!descriptionContainer || !loadingElement) return; - - // Show loading indicator - loadingElement.classList.remove('hidden'); - descriptionContainer.classList.add('hidden'); - - // Determine API endpoint based on file path or context - let apiEndpoint = `/api/loras/model-description?model_id=${modelId}&file_path=${encodeURIComponent(filePath)}`; - - // Try to get model description from API - const response = await fetch(apiEndpoint); - - if (!response.ok) { - throw new Error(`Failed to fetch model description: ${response.statusText}`); - } - - const data = await response.json(); - - if (data.success && data.description) { - // Update the description content - descriptionContainer.innerHTML = data.description; - - // Process any links in the description to open in new tab - const links = descriptionContainer.querySelectorAll('a'); - links.forEach(link => { - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener noreferrer'); - }); - - // Show the description and hide loading indicator - descriptionContainer.classList.remove('hidden'); - loadingElement.classList.add('hidden'); - } else { - throw new Error(data.error || 'No description available'); - } - } catch (error) { - console.error('Error loading model description:', error); - const loadingElement = document.querySelector('.model-description-loading'); - if (loadingElement) { - loadingElement.innerHTML = `
Failed to load model description. ${error.message}
`; - } - - // Show empty state message in the description container - const descriptionContainer = document.querySelector('.model-description-content'); - if (descriptionContainer) { - descriptionContainer.innerHTML = '
No model description available
'; - descriptionContainer.classList.remove('hidden'); - } - } } \ No newline at end of file diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 954a6d78..ba7f807a 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -6,7 +6,7 @@ import { scrollToTop, loadExampleImages } from './showcase/ShowcaseView.js'; -import { setupTabSwitching, loadModelDescription } from './ModelDescription.js'; +import { setupTabSwitching } from './ModelDescription.js'; import { setupModelNameEditing, setupBaseModelEditing, @@ -29,21 +29,21 @@ export function showModelModal(model, modelType) { const modalTitle = model.model_name; // Prepare LoRA specific data - const escapedWords = modelType === 'lora' && model.civitai?.trainedWords?.length ? + const escapedWords = (modelType === 'loras' || modelType === 'embeddings') && model.civitai?.trainedWords?.length ? model.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : []; // Generate model type specific content - const typeSpecificContent = modelType === 'lora' ? renderLoraSpecificContent(model, escapedWords) : ''; + const typeSpecificContent = modelType === 'loras' ? renderLoraSpecificContent(model, escapedWords) : ''; // Generate tabs based on model type - const tabsContent = modelType === 'lora' ? + const tabsContent = modelType === 'loras' ? ` ` : ` `; - const tabPanesContent = modelType === 'lora' ? + const tabPanesContent = modelType === 'loras' ? `
Loading example images... @@ -143,7 +143,7 @@ export function showModelModal(model, modelType) {
- ${model.base_model || (modelType === 'checkpoint' ? 'Unknown' : 'N/A')} + ${model.base_model || 'Unknown'} @@ -206,16 +206,13 @@ export function showModelModal(model, modelType) { setupEventHandlers(model.file_path); // LoRA specific setup - if (modelType === 'lora') { + if (modelType === 'loras' || modelType === 'embeddings') { setupTriggerWordsEditMode(); - // Load recipes for this LoRA - loadRecipesForLora(model.model_name, model.sha256); - } - - // If we have a model ID but no description, fetch it - if (model.civitai?.modelId && !model.modelDescription) { - loadModelDescription(model.civitai.modelId, model.file_path); + if (modelType == 'loras') { + // Load recipes for this LoRA + loadRecipesForLora(model.model_name, model.sha256); + } } // Load example images asynchronously - merge regular and custom images @@ -297,7 +294,7 @@ function setupEventHandlers(filePath) { /** * Set up editable fields (notes and usage tips) in the model modal * @param {string} filePath - The full file path of the model - * @param {string} modelType - Type of model ('lora' or 'checkpoint') + * @param {string} modelType - Type of model ('loras' or 'checkpoints' or 'embeddings') */ function setupEditableFields(filePath, modelType) { const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); @@ -328,13 +325,13 @@ function setupEditableFields(filePath, modelType) { return; } e.preventDefault(); - await saveNotes(filePath, modelType); + await saveNotes(filePath); } }); } // LoRA specific field setup - if (modelType === 'lora') { + if (modelType === 'loras') { setupLoraSpecificFields(filePath); } } @@ -398,9 +395,8 @@ function setupLoraSpecificFields(filePath) { /** * Save model notes * @param {string} filePath - Path to the model file - * @param {string} modelType - Type of model ('lora' or 'checkpoint') */ -async function saveNotes(filePath, modelType) { +async function saveNotes(filePath) { const content = document.querySelector('.notes-content').textContent; try { await getModelApiClient().saveModelMetadata(filePath, { notes: content }); @@ -418,35 +414,4 @@ const modelModal = { scrollToTop }; -export { modelModal }; - -// Define global functions for use in HTML -window.toggleShowcase = function(element) { - toggleShowcase(element); -}; - -window.scrollToTopModel = function(button) { - scrollToTop(button); -}; - -// Legacy global functions for backward compatibility -window.scrollToTopLora = function(button) { - scrollToTop(button); -}; - -window.scrollToTopCheckpoint = function(button) { - scrollToTop(button); -}; - -window.saveModelNotes = function(filePath, modelType) { - saveNotes(filePath, modelType); -}; - -// Legacy functions -window.saveLoraNotes = function(filePath) { - saveNotes(filePath, 'lora'); -}; - -window.saveCheckpointNotes = function(filePath) { - saveNotes(filePath, 'checkpoint'); -}; \ No newline at end of file +export { modelModal }; \ 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 16b67753..d57725e8 100644 --- a/static/js/components/shared/showcase/ShowcaseView.js +++ b/static/js/components/shared/showcase/ShowcaseView.js @@ -112,7 +112,7 @@ export function renderShowcaseContent(images, exampleFiles = [], startExpanded =
` : ''; return ` -
+
Scroll or click to ${startExpanded ? 'hide' : 'show'} ${filteredImages.length} examples
@@ -479,6 +479,16 @@ export function initShowcaseContent(carousel) { initMetadataPanelHandlers(carousel); initMediaControlHandlers(carousel); positionAllMediaControls(carousel); + + // Bind scroll-indicator click to toggleShowcase + const scrollIndicator = carousel.previousElementSibling; + if (scrollIndicator && scrollIndicator.classList.contains('scroll-indicator')) { + // Remove previous click listeners to avoid duplicates + scrollIndicator.onclick = null; + scrollIndicator.removeEventListener('click', scrollIndicator._toggleShowcaseHandler); + scrollIndicator._toggleShowcaseHandler = () => toggleShowcase(scrollIndicator); + scrollIndicator.addEventListener('click', scrollIndicator._toggleShowcaseHandler); + } // Add window resize handler const resizeHandler = () => positionAllMediaControls(carousel); diff --git a/static/js/utils/infiniteScroll.js b/static/js/utils/infiniteScroll.js index 49e4d759..01f732e3 100644 --- a/static/js/utils/infiniteScroll.js +++ b/static/js/utils/infiniteScroll.js @@ -6,9 +6,7 @@ import { showToast } from './uiHelpers.js'; // Function to dynamically import the appropriate card creator based on page type async function getCardCreator(pageType) { - if (pageType === 'loras') { - return (model) => createModelCard(model, 'lora'); - } else if (pageType === 'recipes') { + if (pageType === 'recipes') { // Import the RecipeCard module const { RecipeCard } = await import('../components/RecipeCard.js'); @@ -21,12 +19,11 @@ async function getCardCreator(pageType) { }); return recipeCard.element; }; - } else if (pageType === 'checkpoints') { - return (model) => createModelCard(model, 'checkpoint'); - } else if (pageType === 'embeddings') { - return (model) => createModelCard(model, 'embedding'); } - return null; + + // For other page types, use the shared ModelCard creator + return (model) => createModelCard(model, pageType); + } // Function to get the appropriate data fetcher based on page type @@ -63,14 +60,8 @@ export async function initializeInfiniteScroll(pageType = 'loras') { // Use virtual scrolling for all page types await initializeVirtualScroll(pageType); - // Setup event delegation for lora cards if on the loras page - if (pageType === 'loras') { - setupModelCardEventDelegation('lora'); - } else if (pageType === 'checkpoints') { - setupModelCardEventDelegation('checkpoint'); - } else if (pageType === 'embeddings') { - setupModelCardEventDelegation('embedding'); - } + // Setup event delegation for model cards based on page type + setupModelCardEventDelegation(pageType); } async function initializeVirtualScroll(pageType) {