feat: Refactor showModelModal to fetch complete metadata and update related functions for improved data handling

This commit is contained in:
Will Miao
2025-08-27 19:42:34 +08:00
parent 5b0becaaf2
commit cd0d832f14
2 changed files with 58 additions and 41 deletions

View File

@@ -214,7 +214,7 @@ function handleCardClick(card, modelType) {
} }
} }
function showModelModalFromCard(card, modelType) { async function showModelModalFromCard(card, modelType) {
// Get the appropriate preview versions map // Get the appropriate preview versions map
const previewVersionsKey = modelType; const previewVersionsKey = modelType;
const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map(); const previewVersions = state.pages[previewVersionsKey]?.previewVersions || new Map();
@@ -246,7 +246,7 @@ function showModelModalFromCard(card, modelType) {
}) })
}; };
showModelModal(modelMeta, modelType); await showModelModal(modelMeta, modelType);
} }
// Function to show the example access modal (generalized for lora and checkpoint) // Function to show the example access modal (generalized for lora and checkpoint)
@@ -306,7 +306,7 @@ function showExampleAccessModal(card, modelType) {
// Set up import button // Set up import button
const importBtn = modal.querySelector('#importExamplesBtn'); const importBtn = modal.querySelector('#importExamplesBtn');
if (importBtn) { if (importBtn) {
importBtn.onclick = () => { importBtn.onclick = async () => {
modalManager.closeModal('exampleAccessModal'); modalManager.closeModal('exampleAccessModal');
// Get the model data from card dataset (works for both lora and checkpoint) // Get the model data from card dataset (works for both lora and checkpoint)
@@ -333,7 +333,7 @@ function showExampleAccessModal(card, modelType) {
} }
// Show the model modal // Show the model modal
showModelModal(modelMeta, modelType); await showModelModal(modelMeta, modelType);
// Scroll to import area after modal is visible // Scroll to import area after modal is visible
setTimeout(() => { setTimeout(() => {

View File

@@ -6,7 +6,7 @@ import {
scrollToTop, scrollToTop,
loadExampleImages loadExampleImages
} from './showcase/ShowcaseView.js'; } from './showcase/ShowcaseView.js';
import { setupTabSwitching, setupModelDescriptionEditing } from './ModelDescription.js'; import { setupTabSwitching } from './ModelDescription.js';
import { import {
setupModelNameEditing, setupModelNameEditing,
setupBaseModelEditing, setupBaseModelEditing,
@@ -24,20 +24,38 @@ import { loadRecipesForLora } from './RecipeTab.js';
* @param {Object} model - Model data object * @param {Object} model - Model data object
* @param {string} modelType - Type of model ('lora' or 'checkpoint') * @param {string} modelType - Type of model ('lora' or 'checkpoint')
*/ */
export function showModelModal(model, modelType) { export async function showModelModal(model, modelType) {
const modalId = 'modelModal'; const modalId = 'modelModal';
const modalTitle = model.model_name; const modalTitle = model.model_name;
// Prepare LoRA specific data // Fetch complete civitai metadata
const escapedWords = (modelType === 'loras' || modelType === 'embeddings') && model.civitai?.trainedWords?.length ? let completeCivitaiData = model.civitai || {};
model.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : []; if (model.file_path) {
try {
const fullMetadata = await getModelApiClient().fetchModelMetadata(model.file_path);
completeCivitaiData = fullMetadata || model.civitai || {};
} catch (error) {
console.warn('Failed to fetch complete metadata, using existing data:', error);
// Continue with existing data if fetch fails
}
}
// Update model with complete civitai data
const modelWithFullData = {
...model,
civitai: completeCivitaiData
};
// Prepare LoRA specific data with complete civitai data
const escapedWords = (modelType === 'loras' || modelType === 'embeddings') && modelWithFullData.civitai?.trainedWords?.length ?
modelWithFullData.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : [];
// Generate model type specific content // Generate model type specific content
let typeSpecificContent; let typeSpecificContent;
if (modelType === 'loras') { if (modelType === 'loras') {
typeSpecificContent = renderLoraSpecificContent(model, escapedWords); typeSpecificContent = renderLoraSpecificContent(modelWithFullData, escapedWords);
} else if (modelType === 'embeddings') { } else if (modelType === 'embeddings') {
typeSpecificContent = renderEmbeddingSpecificContent(model, escapedWords); typeSpecificContent = renderEmbeddingSpecificContent(modelWithFullData, escapedWords);
} else { } else {
typeSpecificContent = ''; typeSpecificContent = '';
} }
@@ -45,10 +63,10 @@ export function showModelModal(model, modelType) {
// Generate tabs based on model type // Generate tabs based on model type
const tabsContent = modelType === 'loras' ? 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 === 'loras' ? const tabPanesContent = modelType === 'loras' ?
`<div id="showcase-tab" class="tab-pane active"> `<div id="showcase-tab" class="tab-pane active">
@@ -100,26 +118,26 @@ export function showModelModal(model, modelType) {
</div> </div>
<div class="creator-actions"> <div class="creator-actions">
${model.from_civitai ? ` ${modelWithFullData.from_civitai ? `
<div class="civitai-view" title="View on Civitai" data-action="view-civitai" data-filepath="${model.file_path}"> <div class="civitai-view" title="View on Civitai" data-action="view-civitai" data-filepath="${modelWithFullData.file_path}">
<i class="fas fa-globe"></i> View on Civitai <i class="fas fa-globe"></i> View on Civitai
</div>` : ''} </div>` : ''}
${model.civitai?.creator ? ` ${modelWithFullData.civitai?.creator ? `
<div class="creator-info" data-username="${model.civitai.creator.username}" data-action="view-creator" title="View Creator Profile"> <div class="creator-info" data-username="${modelWithFullData.civitai.creator.username}" data-action="view-creator" title="View Creator Profile">
${model.civitai.creator.image ? ${modelWithFullData.civitai.creator.image ?
`<div class="creator-avatar"> `<div class="creator-avatar">
<img src="${model.civitai.creator.image}" alt="${model.civitai.creator.username}" onerror="this.onerror=null; this.src='static/icons/user-placeholder.png';"> <img src="${modelWithFullData.civitai.creator.image}" alt="${modelWithFullData.civitai.creator.username}" onerror="this.onerror=null; this.src='static/icons/user-placeholder.png';">
</div>` : </div>` :
`<div class="creator-avatar creator-placeholder"> `<div class="creator-avatar creator-placeholder">
<i class="fas fa-user"></i> <i class="fas fa-user"></i>
</div>` </div>`
} }
<span class="creator-username">${model.civitai.creator.username}</span> <span class="creator-username">${modelWithFullData.civitai.creator.username}</span>
</div>` : ''} </div>` : ''}
</div> </div>
${renderCompactTags(model.tags || [], model.file_path)} ${renderCompactTags(modelWithFullData.tags || [], modelWithFullData.file_path)}
</header> </header>
<div class="modal-body"> <div class="modal-body">
@@ -127,12 +145,12 @@ export function showModelModal(model, modelType) {
<div class="info-grid"> <div class="info-grid">
<div class="info-item"> <div class="info-item">
<label>Version</label> <label>Version</label>
<span>${model.civitai?.name || 'N/A'}</span> <span>${modelWithFullData.civitai?.name || 'N/A'}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<label>File Name</label> <label>File Name</label>
<div class="file-name-wrapper"> <div class="file-name-wrapper">
<span id="file-name" class="file-name-content">${model.file_name || 'N/A'}</span> <span id="file-name" class="file-name-content">${modelWithFullData.file_name || 'N/A'}</span>
<button class="edit-file-name-btn" title="Edit file name"> <button class="edit-file-name-btn" title="Edit file name">
<i class="fas fa-pencil-alt"></i> <i class="fas fa-pencil-alt"></i>
</button> </button>
@@ -141,14 +159,14 @@ export function showModelModal(model, modelType) {
<div class="info-item location-size"> <div class="info-item location-size">
<div class="location-wrapper"> <div class="location-wrapper">
<label>Location</label> <label>Location</label>
<span class="file-path">${model.file_path.replace(/[^/]+$/, '') || 'N/A'}</span> <span class="file-path">${modelWithFullData.file_path.replace(/[^/]+$/, '') || 'N/A'}</span>
</div> </div>
</div> </div>
<div class="info-item base-size"> <div class="info-item base-size">
<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 || 'Unknown'}</span> <span class="base-model-content">${modelWithFullData.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>
@@ -156,24 +174,24 @@ export function showModelModal(model, modelType) {
</div> </div>
<div class="size-wrapper"> <div class="size-wrapper">
<label>Size</label> <label>Size</label>
<span>${formatFileSize(model.file_size)}</span> <span>${formatFileSize(modelWithFullData.file_size)}</span>
</div> </div>
</div> </div>
${typeSpecificContent} ${typeSpecificContent}
<div class="info-item notes"> <div class="info-item notes">
<label>Additional Notes <i class="fas fa-info-circle notes-hint" title="Press Enter to save, Shift+Enter for new line"></i></label> <label>Additional Notes <i class="fas fa-info-circle notes-hint" title="Press Enter to save, Shift+Enter for new line"></i></label>
<div class="editable-field"> <div class="editable-field">
<div class="notes-content" contenteditable="true" spellcheck="false">${model.notes || 'Add your notes here...'}</div> <div class="notes-content" contenteditable="true" spellcheck="false">${modelWithFullData.notes || 'Add your notes here...'}</div>
</div> </div>
</div> </div>
<div class="info-item full-width"> <div class="info-item full-width">
<label>About this version</label> <label>About this version</label>
<div class="description-text">${model.civitai?.description || 'N/A'}</div> <div class="description-text">${modelWithFullData.civitai?.description || 'N/A'}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="showcase-section" data-model-hash="${model.sha256 || ''}" data-filepath="${model.file_path}"> <div class="showcase-section" data-model-hash="${modelWithFullData.sha256 || ''}" data-filepath="${modelWithFullData.file_path}">
<div class="showcase-tabs"> <div class="showcase-tabs">
${tabsContent} ${tabsContent}
</div> </div>
@@ -200,16 +218,15 @@ export function showModelModal(model, modelType) {
}; };
modalManager.showModal(modalId, content, null, onCloseCallback); modalManager.showModal(modalId, content, null, onCloseCallback);
setupEditableFields(model.file_path, modelType); setupEditableFields(modelWithFullData.file_path, modelType);
setupShowcaseScroll(modalId); setupShowcaseScroll(modalId);
setupTabSwitching(); setupTabSwitching();
setupTagTooltip(); setupTagTooltip();
setupTagEditMode(); setupTagEditMode();
setupModelNameEditing(model.file_path); setupModelNameEditing(modelWithFullData.file_path);
setupBaseModelEditing(model.file_path); setupBaseModelEditing(modelWithFullData.file_path);
setupFileNameEditing(model.file_path); setupFileNameEditing(modelWithFullData.file_path);
// Remove setupModelDescriptionEditing from here - it will be called lazily setupEventHandlers(modelWithFullData.file_path);
setupEventHandlers(model.file_path);
// LoRA specific setup // LoRA specific setup
if (modelType === 'loras' || modelType === 'embeddings') { if (modelType === 'loras' || modelType === 'embeddings') {
@@ -217,16 +234,16 @@ export function showModelModal(model, modelType) {
if (modelType == 'loras') { if (modelType == 'loras') {
// Load recipes for this LoRA // Load recipes for this LoRA
loadRecipesForLora(model.model_name, model.sha256); loadRecipesForLora(modelWithFullData.model_name, modelWithFullData.sha256);
} }
} }
// Load example images asynchronously - merge regular and custom images // Load example images asynchronously - merge regular and custom images
const regularImages = model.civitai?.images || []; const regularImages = modelWithFullData.civitai?.images || [];
const customImages = model.civitai?.customImages || []; const customImages = modelWithFullData.civitai?.customImages || [];
// Combine images - regular images first, then custom images // Combine images - regular images first, then custom images
const allImages = [...regularImages, ...customImages]; const allImages = [...regularImages, ...customImages];
loadExampleImages(allImages, model.sha256); loadExampleImages(allImages, modelWithFullData.sha256);
} }
function renderLoraSpecificContent(lora, escapedWords) { function renderLoraSpecificContent(lora, escapedWords) {