feat(civitai): add host preference for view links

This commit is contained in:
Will Miao
2026-04-16 13:28:51 +08:00
parent 406d5fea6a
commit 605fbf4117
27 changed files with 574 additions and 37 deletions

View File

@@ -1,7 +1,7 @@
// PageControls.js - Manages controls for both LoRAs and Checkpoints pages
import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js';
import { getStorageItem, setStorageItem, getSessionItem, setSessionItem } from '../../utils/storageHelpers.js';
import { showToast } from '../../utils/uiHelpers.js';
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
import { sidebarManager } from '../SidebarManager.js';
@@ -353,18 +353,8 @@ export class PageControls {
const metaData = JSON.parse(card.dataset.meta);
const civitaiId = metaData.modelId;
const versionId = metaData.id;
// Build URL
if (civitaiId) {
let url = `https://civitai.com/models/${civitaiId}`;
if (versionId) {
url += `?modelVersionId=${versionId}`;
}
window.open(url, '_blank');
} else {
// If no ID, try searching by name
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
}
openCivitaiByMetadata(civitaiId, versionId, modelName);
}
/**

View File

@@ -1,26 +1,21 @@
import { getModelApiClient } from '../../api/modelApiFactory.js';
import { downloadManager } from '../../managers/DownloadManager.js';
import { modalManager } from '../../managers/ModalManager.js';
import { showToast } from '../../utils/uiHelpers.js';
import { openCivitaiUrl, showToast } from '../../utils/uiHelpers.js';
import { translate } from '../../utils/i18nHelpers.js';
import { state } from '../../state/index.js';
import { buildCivitaiModelUrl } from '../../utils/civitaiUtils.js';
import { formatFileSize } from './utils.js';
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.mov', '.mkv'];
const PREVIEW_PLACEHOLDER_URL = '/loras_static/images/no-preview.png';
function buildCivitaiVersionUrl(modelId, versionId) {
if (modelId == null || versionId == null) {
return null;
}
const normalizedModelId = String(modelId).trim();
const normalizedVersionId = String(versionId).trim();
if (!normalizedModelId || !normalizedVersionId) {
return null;
}
const encodedModelId = encodeURIComponent(normalizedModelId);
const encodedVersionId = encodeURIComponent(normalizedVersionId);
return `https://civitai.com/models/${encodedModelId}?modelVersionId=${encodedVersionId}`;
return buildCivitaiModelUrl(
modelId,
versionId,
state?.global?.settings?.civitai_host
);
}
function escapeHtml(value) {
@@ -1352,6 +1347,13 @@ export function initVersionsTab({
}
const row = event.target.closest('.model-version-row.is-clickable');
const civitaiLink = event.target.closest('.version-civitai-link');
if (civitaiLink) {
event.preventDefault();
openCivitaiUrl(civitaiLink.href);
return;
}
if (!row) {
return;
}
@@ -1371,7 +1373,7 @@ export function initVersionsTab({
return;
}
event.preventDefault();
window.open(targetUrl, '_blank', 'noopener,noreferrer');
openCivitaiUrl(targetUrl);
});
// Listen for extension-triggered refresh requests

View File

@@ -802,6 +802,11 @@ export class SettingsManager {
usePortableCheckbox.checked = !!state.global.settings.use_portable_settings;
}
const civitaiHostSelect = document.getElementById('civitaiHost');
if (civitaiHostSelect) {
civitaiHostSelect.value = state.global.settings.civitai_host || 'civitai.com';
}
const recipesPathInput = document.getElementById('recipesPath');
if (recipesPathInput) {
recipesPathInput.value = state.global.settings.recipes_path || '';

View File

@@ -5,6 +5,7 @@ import { DEFAULT_PATH_TEMPLATES, DEFAULT_PRIORITY_TAG_CONFIG } from '../utils/co
const DEFAULT_SETTINGS_BASE = Object.freeze({
civitai_api_key: '',
civitai_host: 'civitai.com',
use_portable_settings: false,
language: 'en',
show_only_sfw: false,

View File

@@ -13,11 +13,64 @@ export const OptimizationMode = {
THUMBNAIL: 'thumbnail',
};
export const DEFAULT_CIVITAI_PAGE_HOST = 'civitai.com';
const SUPPORTED_CIVITAI_PAGE_HOSTS = new Set([
'civitai.com',
'civitai.red',
]);
export function normalizeCivitaiPageHost(hostname) {
if (!hostname || typeof hostname !== 'string') {
return DEFAULT_CIVITAI_PAGE_HOST;
}
const normalized = hostname.trim().toLowerCase();
if (SUPPORTED_CIVITAI_PAGE_HOSTS.has(normalized)) {
return normalized;
}
return DEFAULT_CIVITAI_PAGE_HOST;
}
export function buildCivitaiModelUrl(modelId, versionId = null, host = DEFAULT_CIVITAI_PAGE_HOST) {
const normalizedHost = normalizeCivitaiPageHost(host);
const normalizedModelId = modelId == null ? '' : String(modelId).trim();
const normalizedVersionId = versionId == null ? '' : String(versionId).trim();
if (normalizedModelId) {
const encodedModelId = encodeURIComponent(normalizedModelId);
let url = `https://${normalizedHost}/models/${encodedModelId}`;
if (normalizedVersionId) {
url += `?modelVersionId=${encodeURIComponent(normalizedVersionId)}`;
}
return url;
}
if (normalizedVersionId) {
return `https://${normalizedHost}/model-versions/${encodeURIComponent(normalizedVersionId)}`;
}
return null;
}
export function buildCivitaiSearchUrl(query, host = DEFAULT_CIVITAI_PAGE_HOST) {
const normalizedQuery = query == null ? '' : String(query).trim();
if (!normalizedQuery) {
return null;
}
const normalizedHost = normalizeCivitaiPageHost(host);
return `https://${normalizedHost}/models?query=${encodeURIComponent(normalizedQuery)}`;
}
export function buildCivitaiUrl({ modelId = null, versionId = null, modelName = null, host = DEFAULT_CIVITAI_PAGE_HOST } = {}) {
return (
buildCivitaiModelUrl(modelId, versionId, host)
|| buildCivitaiSearchUrl(modelName, host)
);
}
/**
* Rewrite Civitai preview URLs to use optimized renditions.
* Mirrors the backend's rewrite_preview_url() function from py/utils/civitai_utils.py

View File

@@ -3,6 +3,66 @@ import { state, getCurrentPageState } from '../state/index.js';
import { getStorageItem, setStorageItem } from './storageHelpers.js';
import { NODE_TYPE_ICONS, DEFAULT_NODE_COLOR } from './constants.js';
import { eventManager } from './EventManager.js';
import { bannerService } from '../managers/BannerService.js';
import { modalManager } from '../managers/ModalManager.js';
import { buildCivitaiUrl, normalizeCivitaiPageHost } from './civitaiUtils.js';
const CIVITAI_HOST_INFO_BANNER_ID = 'civitai-host-preference';
const CIVITAI_HOST_INFO_BANNER_SEEN_KEY = 'civitai_host_info_banner_seen';
function getPreferredCivitaiHost() {
return normalizeCivitaiPageHost(state?.global?.settings?.civitai_host);
}
function maybeRegisterCivitaiHostInfoBanner() {
if (getStorageItem(CIVITAI_HOST_INFO_BANNER_SEEN_KEY, false)) {
return;
}
setStorageItem(CIVITAI_HOST_INFO_BANNER_SEEN_KEY, true);
bannerService.registerBanner(CIVITAI_HOST_INFO_BANNER_ID, {
id: CIVITAI_HOST_INFO_BANNER_ID,
title: translate(
'settings.civitaiHostBanner.title',
{},
'Civitai host preference available'
),
content: translate(
'settings.civitaiHostBanner.content',
{},
'Civitai now uses civitai.com for SFW content and civitai.red for unrestricted content. You can change which site opens by default in Settings.'
),
actions: [
{
text: translate('settings.civitaiHostBanner.openSettings', {}, 'Open Settings'),
icon: 'fas fa-cog',
action: 'open-settings-modal',
type: 'primary',
},
],
dismissible: true,
priority: 70,
onRegister: (bannerElement) => {
const button = bannerElement.querySelector('.banner-action[data-action="open-settings-modal"]');
if (button) {
button.addEventListener('click', (event) => {
event.preventDefault();
modalManager.showModal('settingsModal');
});
}
},
});
}
export function openCivitaiUrl(url) {
if (!url) {
return null;
}
maybeRegisterCivitaiHostInfoBanner();
return window.open(url, '_blank', 'noopener,noreferrer');
}
/**
* Utility function to copy text to clipboard with fallback for older browsers
@@ -184,14 +244,15 @@ function filterByFolder(folderPath) {
}
export function openCivitaiByMetadata(civitaiId, versionId, modelName = null) {
if (versionId) {
// Use model-versions endpoint which auto-redirects to correct model page
window.open(`https://civitai.com/model-versions/${versionId}`, '_blank');
} else if (civitaiId) {
window.open(`https://civitai.com/models/${civitaiId}`, '_blank');
} else if (modelName) {
// Fallback: search by name
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
const url = buildCivitaiUrl({
modelId: civitaiId,
versionId,
modelName,
host: getPreferredCivitaiHost(),
});
if (url) {
openCivitaiUrl(url);
}
}