refactor: update model type references from 'lora' to 'loras' and streamline event delegation setup

This commit is contained in:
Will Miao
2025-07-25 22:33:46 +08:00
parent 0d9003dea4
commit 12d1857b13
5 changed files with 47 additions and 152 deletions

View File

@@ -153,7 +153,7 @@ async function toggleFavorite(card) {
} }
function handleSendToWorkflow(card, replaceMode, modelType) { function handleSendToWorkflow(card, replaceMode, modelType) {
if (modelType === 'lora') { if (modelType === 'loras') {
const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
const strength = usageTips.strength || 1; const strength = usageTips.strength || 1;
const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`; const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
@@ -165,15 +165,18 @@ function handleSendToWorkflow(card, replaceMode, modelType) {
} }
function handleCopyAction(card, modelType) { function handleCopyAction(card, modelType) {
if (modelType === 'lora') { if (modelType === 'loras') {
const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
const strength = usageTips.strength || 1; const strength = usageTips.strength || 1;
const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`; const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard'); copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard');
} else { } else if (modelType === 'checkpoints') {
// Checkpoint copy functionality - copy checkpoint name // Checkpoint copy functionality - copy checkpoint name
const checkpointName = card.dataset.file_name; const checkpointName = card.dataset.file_name;
copyToClipboard(checkpointName, 'Checkpoint name copied'); 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) { function showModelModalFromCard(card, modelType) {
// Get the appropriate preview versions map // Get the appropriate preview versions map
const previewVersionsKey = modelType === 'lora' ? 'loras' : 'checkpoints'; const previewVersionsKey = modelType;
const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map(); const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map();
const version = previewVersions.get(card.dataset.filepath); const version = previewVersions.get(card.dataset.filepath);
const previewUrl = card.dataset.preview_url || '/loras_static/images/no-preview.png'; 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.file_size = model.file_size;
card.dataset.from_civitai = model.from_civitai; card.dataset.from_civitai = model.from_civitai;
card.dataset.notes = model.notes || ''; 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'; card.dataset.favorite = model.favorite ? 'true' : 'false';
// LoRA specific data // LoRA specific data
if (modelType === 'lora') { if (modelType === 'loras') {
card.dataset.usage_tips = model.usage_tips; 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) // 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'); card.classList.add('selected');
} }
// Get the appropriate preview versions map // Get the appropriate preview versions map
const previewVersionsKey = modelType === 'lora' ? 'loras' : 'checkpoints'; const previewVersionsKey = modelType;
const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map(); const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map();
const version = previewVersions.get(model.file_path); const version = previewVersions.get(model.file_path);
const previewUrl = model.preview_url || '/loras_static/images/no-preview.png'; 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; const isFavorite = model.favorite === true;
// Generate action icons based on model type // Generate action icons based on model type
const actionIcons = modelType === 'lora' ? const actionIcons = `
`<i class="${isFavorite ? 'fas fa-star favorite-active' : 'far fa-star'}" <i class="${isFavorite ? 'fas fa-star favorite-active' : 'far fa-star'}"
title="${isFavorite ? 'Remove from favorites' : 'Add to favorites'}"> title="${isFavorite ? 'Remove from favorites' : 'Add to favorites'}">
</i> </i>
<i class="fas fa-globe" <i class="fas fa-globe"
@@ -447,19 +450,6 @@ export function createModelCard(model, modelType) {
</i> </i>
<i class="fas fa-copy" <i class="fas fa-copy"
title="Copy LoRA Syntax"> title="Copy LoRA Syntax">
</i>` :
`<i class="${isFavorite ? 'fas fa-star favorite-active' : 'far fa-star'}"
title="${isFavorite ? 'Remove from favorites' : 'Add to favorites'}">
</i>
<i class="fas fa-globe"
title="${model.from_civitai ? 'View on Civitai' : 'Not available from Civitai'}"
${!model.from_civitai ? 'style="opacity: 0.5; cursor: not-allowed"' : ''}>
</i>
<i class="fas fa-paper-plane"
title="Send to workflow - feature to be implemented">
</i>
<i class="fas fa-copy"
title="Copy Checkpoint Name">
</i>`; </i>`;
card.innerHTML = ` card.innerHTML = `

View File

@@ -41,64 +41,3 @@ 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 = `<div class="error-message">Failed to load model description. ${error.message}</div>`;
}
// Show empty state message in the description container
const descriptionContainer = document.querySelector('.model-description-content');
if (descriptionContainer) {
descriptionContainer.innerHTML = '<div class="no-description">No model description available</div>';
descriptionContainer.classList.remove('hidden');
}
}
}

View File

@@ -6,7 +6,7 @@ import {
scrollToTop, scrollToTop,
loadExampleImages loadExampleImages
} from './showcase/ShowcaseView.js'; } from './showcase/ShowcaseView.js';
import { setupTabSwitching, loadModelDescription } from './ModelDescription.js'; import { setupTabSwitching } from './ModelDescription.js';
import { import {
setupModelNameEditing, setupModelNameEditing,
setupBaseModelEditing, setupBaseModelEditing,
@@ -29,21 +29,21 @@ export function showModelModal(model, modelType) {
const modalTitle = model.model_name; const modalTitle = model.model_name;
// Prepare LoRA specific data // 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, '\\\'')) : []; model.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : [];
// Generate model type specific content // 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 // Generate tabs based on model type
const tabsContent = modelType === 'lora' ? const tabsContent = modelType === 'loras' ?
`<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>
<button class="tab-btn" data-tab="recipes">Recipes</button>` : <button class="tab-btn" data-tab="recipes">Recipes</button>` :
`<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>`;
const tabPanesContent = modelType === 'lora' ? const tabPanesContent = modelType === 'loras' ?
`<div id="showcase-tab" class="tab-pane active"> `<div id="showcase-tab" class="tab-pane active">
<div class="example-images-loading"> <div class="example-images-loading">
<i class="fas fa-spinner fa-spin"></i> Loading example images... <i class="fas fa-spinner fa-spin"></i> Loading example images...
@@ -143,7 +143,7 @@ export function showModelModal(model, modelType) {
<div class="base-wrapper"> <div class="base-wrapper">
<label>Base Model</label> <label>Base Model</label>
<div class="base-model-display"> <div class="base-model-display">
<span class="base-model-content">${model.base_model || (modelType === 'checkpoint' ? 'Unknown' : 'N/A')}</span> <span class="base-model-content">${model.base_model || 'Unknown'}</span>
<button class="edit-base-model-btn" title="Edit base model"> <button class="edit-base-model-btn" title="Edit base model">
<i class="fas fa-pencil-alt"></i> <i class="fas fa-pencil-alt"></i>
</button> </button>
@@ -206,16 +206,13 @@ export function showModelModal(model, modelType) {
setupEventHandlers(model.file_path); setupEventHandlers(model.file_path);
// LoRA specific setup // LoRA specific setup
if (modelType === 'lora') { if (modelType === 'loras' || modelType === 'embeddings') {
setupTriggerWordsEditMode(); setupTriggerWordsEditMode();
// Load recipes for this LoRA if (modelType == 'loras') {
loadRecipesForLora(model.model_name, model.sha256); // 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);
} }
// Load example images asynchronously - merge regular and custom images // 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 * Set up editable fields (notes and usage tips) in the model modal
* @param {string} filePath - The full file path of the model * @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) { function setupEditableFields(filePath, modelType) {
const editableFields = document.querySelectorAll('.editable-field [contenteditable]'); const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
@@ -328,13 +325,13 @@ function setupEditableFields(filePath, modelType) {
return; return;
} }
e.preventDefault(); e.preventDefault();
await saveNotes(filePath, modelType); await saveNotes(filePath);
} }
}); });
} }
// LoRA specific field setup // LoRA specific field setup
if (modelType === 'lora') { if (modelType === 'loras') {
setupLoraSpecificFields(filePath); setupLoraSpecificFields(filePath);
} }
} }
@@ -398,9 +395,8 @@ function setupLoraSpecificFields(filePath) {
/** /**
* Save model notes * Save model notes
* @param {string} filePath - Path to the model file * @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; const content = document.querySelector('.notes-content').textContent;
try { try {
await getModelApiClient().saveModelMetadata(filePath, { notes: content }); await getModelApiClient().saveModelMetadata(filePath, { notes: content });
@@ -419,34 +415,3 @@ const modelModal = {
}; };
export { modelModal }; 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');
};

View File

@@ -112,7 +112,7 @@ export function renderShowcaseContent(images, exampleFiles = [], startExpanded =
</div>` : ''; </div>` : '';
return ` return `
<div class="scroll-indicator" onclick="toggleShowcase(this)"> <div class="scroll-indicator">
<i class="fas fa-chevron-${startExpanded ? 'up' : 'down'}"></i> <i class="fas fa-chevron-${startExpanded ? 'up' : 'down'}"></i>
<span>Scroll or click to ${startExpanded ? 'hide' : 'show'} ${filteredImages.length} examples</span> <span>Scroll or click to ${startExpanded ? 'hide' : 'show'} ${filteredImages.length} examples</span>
</div> </div>
@@ -480,6 +480,16 @@ export function initShowcaseContent(carousel) {
initMediaControlHandlers(carousel); initMediaControlHandlers(carousel);
positionAllMediaControls(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 // Add window resize handler
const resizeHandler = () => positionAllMediaControls(carousel); const resizeHandler = () => positionAllMediaControls(carousel);
window.removeEventListener('resize', resizeHandler); window.removeEventListener('resize', resizeHandler);

View File

@@ -6,9 +6,7 @@ import { showToast } from './uiHelpers.js';
// Function to dynamically import the appropriate card creator based on page type // Function to dynamically import the appropriate card creator based on page type
async function getCardCreator(pageType) { async function getCardCreator(pageType) {
if (pageType === 'loras') { if (pageType === 'recipes') {
return (model) => createModelCard(model, 'lora');
} else if (pageType === 'recipes') {
// Import the RecipeCard module // Import the RecipeCard module
const { RecipeCard } = await import('../components/RecipeCard.js'); const { RecipeCard } = await import('../components/RecipeCard.js');
@@ -21,12 +19,11 @@ async function getCardCreator(pageType) {
}); });
return recipeCard.element; 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 // 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 // Use virtual scrolling for all page types
await initializeVirtualScroll(pageType); await initializeVirtualScroll(pageType);
// Setup event delegation for lora cards if on the loras page // Setup event delegation for model cards based on page type
if (pageType === 'loras') { setupModelCardEventDelegation(pageType);
setupModelCardEventDelegation('lora');
} else if (pageType === 'checkpoints') {
setupModelCardEventDelegation('checkpoint');
} else if (pageType === 'embeddings') {
setupModelCardEventDelegation('embedding');
}
} }
async function initializeVirtualScroll(pageType) { async function initializeVirtualScroll(pageType) {