/** * MetadataPanel - Right panel for model metadata and tabs * Features: * - Fixed header with model info * - Compact metadata grid * - Editable fields (usage tips, trigger words, notes) * - Tabs with accordion content */ import { escapeHtml, formatFileSize } from '../shared/utils.js'; import { translate } from '../../utils/i18nHelpers.js'; export class MetadataPanel { constructor(container) { this.element = container; this.model = null; this.modelType = null; this.activeTab = 'description'; } /** * Render the metadata panel */ render({ model, modelType }) { this.model = model; this.modelType = modelType; this.element.innerHTML = this.getTemplate(); this.bindEvents(); } /** * Get the HTML template */ getTemplate() { const m = this.model; const civitai = m.civitai || {}; const creator = civitai.creator || {}; return `
${this.modelType === 'loras' ? this.renderLoraSpecific() : ''} ${this.renderNotes(m.notes)} `; } /** * Render license icons */ renderLicenseIcons() { const license = this.model.civitai?.model; if (!license) return ''; const icons = []; if (license.allowNoCredit === false) { icons.push({ icon: 'user-check', title: translate('modals.model.license.creditRequired', {}, 'Creator credit required') }); } if (license.allowCommercialUse) { const restrictions = this.resolveCommercialRestrictions(license.allowCommercialUse); restrictions.forEach(r => { icons.push({ icon: r.icon, title: r.title }); }); } if (license.allowDerivatives === false) { icons.push({ icon: 'exchange-off', title: translate('modals.model.license.noDerivatives', {}, 'No sharing merges') }); } if (license.allowDifferentLicense === false) { icons.push({ icon: 'rotate-2', title: translate('modals.model.license.noReLicense', {}, 'Same permissions required') }); } if (icons.length === 0) return ''; return ` `; } /** * Resolve commercial restrictions */ resolveCommercialRestrictions(value) { const COMMERCIAL_CONFIG = [ { key: 'image', icon: 'photo-off', title: translate('modals.model.license.noImageSell', {}, 'No selling generated content') }, { key: 'rentcivit', icon: 'brush-off', title: translate('modals.model.license.noRentCivit', {}, 'No Civitai generation') }, { key: 'rent', icon: 'world-off', title: translate('modals.model.license.noRent', {}, 'No generation services') }, { key: 'sell', icon: 'shopping-cart-off', title: translate('modals.model.license.noSell', {}, 'No selling models') }, ]; // Parse and normalize values let allowed = new Set(); const values = Array.isArray(value) ? value : [value]; values.forEach(v => { if (!v && v !== '') return; const cleaned = String(v).trim().toLowerCase().replace(/[\s_-]+/g, '').replace(/[^a-z]/g, ''); if (cleaned) allowed.add(cleaned); }); // Apply hierarchy if (allowed.has('sell')) { allowed.add('rent'); allowed.add('rentcivit'); allowed.add('image'); } if (allowed.has('rent')) { allowed.add('rentcivit'); } // Return disallowed items return COMMERCIAL_CONFIG.filter(config => !allowed.has(config.key)); } /** * Render tags */ renderTags(tags) { if (!tags || tags.length === 0) return ''; const visibleTags = tags.slice(0, 8); const remaining = tags.length - visibleTags.length; return ` `; } /** * Render LoRA specific sections */ renderLoraSpecific() { const m = this.model; const usageTips = m.usage_tips ? JSON.parse(m.usage_tips) : {}; const triggerWords = m.civitai?.trainedWords || []; return ` `; } /** * Render notes section */ renderNotes(notes) { return ` `; } /** * Render tabs */ renderTabs() { const tabs = [ { id: 'description', label: translate('modals.model.tabs.description', {}, 'Description') }, { id: 'versions', label: translate('modals.model.tabs.versions', {}, 'Versions') }, ]; if (this.modelType === 'loras') { tabs.push({ id: 'recipes', label: translate('modals.model.tabs.recipes', {}, 'Recipes') }); } return `${translate('modals.model.description.empty', {}, 'No description available')}
`}