import { showToast, openCivitai } from '../../utils/uiHelpers.js';
import { modalManager } from '../../managers/ModalManager.js';
import {
toggleShowcase,
setupShowcaseScroll,
scrollToTop,
loadExampleImages
} from './showcase/ShowcaseView.js';
import { setupTabSwitching } from './ModelDescription.js';
import {
setupModelNameEditing,
setupBaseModelEditing,
setupFileNameEditing
} from './ModelMetadata.js';
import { setupTagEditMode } from './ModelTags.js';
import { getModelApiClient } from '../../api/modelApiFactory.js';
import { renderCompactTags, setupTagTooltip, formatFileSize } from './utils.js';
import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js';
import { parsePresets, renderPresetTags } from './PresetTags.js';
import { initVersionsTab } from './ModelVersionsTab.js';
import { loadRecipesForLora } from './RecipeTab.js';
import { translate } from '../../utils/i18nHelpers.js';
function getModalFilePath(fallback = '') {
const modalElement = document.getElementById('modelModal');
if (modalElement && modalElement.dataset && modalElement.dataset.filePath) {
return modalElement.dataset.filePath;
}
return fallback;
}
const COMMERCIAL_ICON_CONFIG = [
{
key: 'sell',
icon: 'shopping-cart-off.svg',
titleKey: 'modals.model.license.noSell',
fallback: 'Selling not allowed'
},
{
key: 'rent',
icon: 'world-off.svg',
titleKey: 'modals.model.license.noRent',
fallback: 'Rental not allowed'
},
{
key: 'rentcivit',
icon: 'brush-off.svg',
titleKey: 'modals.model.license.noRentCivit',
fallback: 'Civitai rental not allowed'
},
{
key: 'image',
icon: 'photo-off.svg',
titleKey: 'modals.model.license.noImageUse',
fallback: 'Image use not allowed'
}
];
function hasLicenseField(license, field) {
return Object.prototype.hasOwnProperty.call(license || {}, field);
}
function escapeAttribute(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/"/g, '"');
}
function indentMarkup(markup, spaces) {
if (!markup) {
return '';
}
const padding = ' '.repeat(spaces);
return markup
.split('\n')
.map(line => (line ? padding + line : line))
.join('\n');
}
function normalizeCommercialValues(value) {
if (!value && value !== '') {
return ['Sell'];
}
if (Array.isArray(value)) {
return value.filter(item => item !== null && item !== undefined);
}
if (typeof value === 'string') {
return [value];
}
if (value && typeof value[Symbol.iterator] === 'function') {
const result = [];
for (const item of value) {
if (item === null || item === undefined) {
continue;
}
result.push(String(item));
}
if (result.length > 0) {
return result;
}
}
return ['Sell'];
}
function sanitiseCommercialValue(value) {
if (!value && value !== '') {
return '';
}
return String(value)
.trim()
.toLowerCase()
.replace(/[\s_-]+/g, '')
.replace(/[^a-z]/g, '');
}
function resolveCommercialRestrictions(value) {
const normalizedValues = normalizeCommercialValues(value);
const allowed = new Set();
normalizedValues.forEach(item => {
const cleaned = sanitiseCommercialValue(item);
if (!cleaned) {
return;
}
allowed.add(cleaned);
});
if (allowed.has('sell')) {
allowed.add('rent');
allowed.add('rentcivit');
allowed.add('image');
}
if (allowed.has('rent')) {
allowed.add('rentcivit');
}
const disallowed = [];
COMMERCIAL_ICON_CONFIG.forEach(config => {
if (!allowed.has(config.key)) {
disallowed.push(config);
}
});
return disallowed;
}
function createLicenseIconMarkup(icon, label) {
const safeLabel = escapeAttribute(label);
const iconPath = `/loras_static/images/tabler/${icon}`;
return ``;
}
function renderLicenseIcons(modelData) {
const license = modelData?.civitai?.model;
if (!license) {
return '';
}
const icons = [];
if (hasLicenseField(license, 'allowNoCredit') && license.allowNoCredit === false) {
const label = translate('modals.model.license.creditRequired', {}, 'Credit required');
icons.push(createLicenseIconMarkup('user-check.svg', label));
}
if (hasLicenseField(license, 'allowCommercialUse')) {
const restrictions = resolveCommercialRestrictions(license.allowCommercialUse);
restrictions.forEach(({ icon, titleKey, fallback }) => {
const label = translate(titleKey, {}, fallback);
icons.push(createLicenseIconMarkup(icon, label));
});
}
if (hasLicenseField(license, 'allowDerivatives') && license.allowDerivatives === false) {
const label = translate('modals.model.license.noDerivatives', {}, 'Derivatives not allowed');
icons.push(createLicenseIconMarkup('exchange-off.svg', label));
}
if (hasLicenseField(license, 'allowDifferentLicense') && license.allowDifferentLicense === false) {
const label = translate('modals.model.license.noReLicense', {}, 'Relicensing not allowed');
icons.push(createLicenseIconMarkup('rotate-2.svg', label));
}
if (!icons.length) {
return '';
}
const containerLabel = translate('modals.model.license.restrictionsLabel', {}, 'License restrictions');
const safeContainerLabel = escapeAttribute(containerLabel);
return `
${icons.join('\n ')}
`;
}
/**
* Display the model modal with the given model data
* @param {Object} model - Model data object
* @param {string} modelType - Type of model ('lora' or 'checkpoint')
*/
export async function showModelModal(model, modelType) {
const modalId = 'modelModal';
const modalTitle = model.model_name;
// Fetch complete civitai metadata
let completeCivitaiData = model.civitai || {};
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
};
const licenseIcons = renderLicenseIcons(modelWithFullData);
const viewOnCivitaiAction = modelWithFullData.from_civitai ? `
${translate('modals.model.actions.viewOnCivitaiText', {}, 'View on Civitai')}