mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
refactor: update model type references from 'lora' to 'loras' and streamline event delegation setup
This commit is contained in:
@@ -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 = `
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user