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) {
if (modelType === 'lora') {
if (modelType === 'loras') {
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
const strength = usageTips.strength || 1;
const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
@@ -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 = `<lora:${card.dataset.file_name}:${strength}>`;
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' ?
`<i class="${isFavorite ? 'fas fa-star favorite-active' : 'far fa-star'}"
const actionIcons = `
<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"
@@ -447,19 +450,6 @@ export function createModelCard(model, modelType) {
</i>
<i class="fas fa-copy"
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>`;
card.innerHTML = `

View File

@@ -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 = `<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,
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' ?
`<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="recipes">Recipes</button>` :
`<button class="tab-btn active" data-tab="showcase">Examples</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 class="example-images-loading">
<i class="fas fa-spinner fa-spin"></i> Loading example images...
@@ -143,7 +143,7 @@ export function showModelModal(model, modelType) {
<div class="base-wrapper">
<label>Base Model</label>
<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">
<i class="fas fa-pencil-alt"></i>
</button>
@@ -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');
};
export { modelModal };

View File

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

View File

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