mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
feat: implement event delegation for checkpoint cards and enhance Civitai link handling
This commit is contained in:
@@ -1,10 +1,203 @@
|
|||||||
import { showToast, copyToClipboard, openExampleImagesFolder } from '../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, openExampleImagesFolder, openCivitai } from '../utils/uiHelpers.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { showCheckpointModal } from './checkpointModal/index.js';
|
import { showCheckpointModal } from './checkpointModal/index.js';
|
||||||
import { NSFW_LEVELS } from '../utils/constants.js';
|
import { NSFW_LEVELS } from '../utils/constants.js';
|
||||||
import { replaceCheckpointPreview as apiReplaceCheckpointPreview, saveModelMetadata } from '../api/checkpointApi.js';
|
import { replaceCheckpointPreview as apiReplaceCheckpointPreview, saveModelMetadata } from '../api/checkpointApi.js';
|
||||||
import { showDeleteModal } from '../utils/modalUtils.js';
|
import { showDeleteModal } from '../utils/modalUtils.js';
|
||||||
|
|
||||||
|
// Add a global event delegation handler
|
||||||
|
export function setupCheckpointCardEventDelegation() {
|
||||||
|
const gridElement = document.getElementById('checkpointGrid');
|
||||||
|
if (!gridElement) return;
|
||||||
|
|
||||||
|
// Remove any existing event listener to prevent duplication
|
||||||
|
gridElement.removeEventListener('click', handleCheckpointCardEvent);
|
||||||
|
|
||||||
|
// Add the event delegation handler
|
||||||
|
gridElement.addEventListener('click', handleCheckpointCardEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event delegation handler for all checkpoint card events
|
||||||
|
function handleCheckpointCardEvent(event) {
|
||||||
|
// Find the closest card element
|
||||||
|
const card = event.target.closest('.lora-card');
|
||||||
|
if (!card) return;
|
||||||
|
|
||||||
|
// Handle specific elements within the card
|
||||||
|
if (event.target.closest('.toggle-blur-btn')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
toggleBlurContent(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.show-content-btn')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
showBlurredContent(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-star')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
toggleFavorite(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-globe')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (card.dataset.from_civitai === 'true') {
|
||||||
|
openCivitai(card.dataset.filepath);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-copy')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
copyCheckpointName(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-trash')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
showDeleteModal(card.dataset.filepath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-image')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
replaceCheckpointPreview(card.dataset.filepath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-folder-open')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
openExampleImagesFolder(card.dataset.sha256);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no specific element was clicked, handle the card click (show modal)
|
||||||
|
showCheckpointModalFromCard(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for event handling
|
||||||
|
function toggleBlurContent(card) {
|
||||||
|
const preview = card.querySelector('.card-preview');
|
||||||
|
const isBlurred = preview.classList.toggle('blurred');
|
||||||
|
const icon = card.querySelector('.toggle-blur-btn i');
|
||||||
|
|
||||||
|
// Update the icon based on blur state
|
||||||
|
if (isBlurred) {
|
||||||
|
icon.className = 'fas fa-eye';
|
||||||
|
} else {
|
||||||
|
icon.className = 'fas fa-eye-slash';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle the overlay visibility
|
||||||
|
const overlay = card.querySelector('.nsfw-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = isBlurred ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBlurredContent(card) {
|
||||||
|
const preview = card.querySelector('.card-preview');
|
||||||
|
preview.classList.remove('blurred');
|
||||||
|
|
||||||
|
// Update the toggle button icon
|
||||||
|
const toggleBtn = card.querySelector('.toggle-blur-btn');
|
||||||
|
if (toggleBtn) {
|
||||||
|
toggleBtn.querySelector('i').className = 'fas fa-eye-slash';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the overlay
|
||||||
|
const overlay = card.querySelector('.nsfw-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleFavorite(card) {
|
||||||
|
const starIcon = card.querySelector('.fa-star');
|
||||||
|
const isFavorite = starIcon.classList.contains('fas');
|
||||||
|
const newFavoriteState = !isFavorite;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save the new favorite state to the server
|
||||||
|
await saveModelMetadata(card.dataset.filepath, {
|
||||||
|
favorite: newFavoriteState
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newFavoriteState) {
|
||||||
|
showToast('Added to favorites', 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Removed from favorites', 'success');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update favorite status:', error);
|
||||||
|
showToast('Failed to update favorite status', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyCheckpointName(card) {
|
||||||
|
const checkpointName = card.dataset.file_name;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await copyToClipboard(checkpointName, 'Checkpoint name copied');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Copy failed:', err);
|
||||||
|
showToast('Copy failed', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCheckpointModalFromCard(card) {
|
||||||
|
// Get the page-specific previewVersions map
|
||||||
|
const previewVersions = state.pages.checkpoints.previewVersions || new Map();
|
||||||
|
const version = previewVersions.get(card.dataset.filepath);
|
||||||
|
const previewUrl = card.dataset.preview_url || '/loras_static/images/no-preview.png';
|
||||||
|
const versionedPreviewUrl = version ? `${previewUrl}?t=${version}` : previewUrl;
|
||||||
|
|
||||||
|
// Show checkpoint details modal
|
||||||
|
const checkpointMeta = {
|
||||||
|
sha256: card.dataset.sha256,
|
||||||
|
file_path: card.dataset.filepath,
|
||||||
|
model_name: card.dataset.name,
|
||||||
|
file_name: card.dataset.file_name,
|
||||||
|
folder: card.dataset.folder,
|
||||||
|
modified: card.dataset.modified,
|
||||||
|
file_size: parseInt(card.dataset.file_size || '0'),
|
||||||
|
from_civitai: card.dataset.from_civitai === 'true',
|
||||||
|
base_model: card.dataset.base_model,
|
||||||
|
notes: card.dataset.notes || '',
|
||||||
|
preview_url: versionedPreviewUrl,
|
||||||
|
// Parse civitai metadata from the card's dataset
|
||||||
|
civitai: (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(card.dataset.meta || '{}');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse civitai metadata:', e);
|
||||||
|
return {}; // Return empty object on error
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
tags: (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(card.dataset.tags || '[]');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse tags:', e);
|
||||||
|
return []; // Return empty array on error
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
modelDescription: card.dataset.modelDescription || ''
|
||||||
|
};
|
||||||
|
showCheckpointModal(checkpointMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceCheckpointPreview(filePath) {
|
||||||
|
if (window.replaceCheckpointPreview) {
|
||||||
|
window.replaceCheckpointPreview(filePath);
|
||||||
|
} else {
|
||||||
|
apiReplaceCheckpointPreview(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createCheckpointCard(checkpoint) {
|
export function createCheckpointCard(checkpoint) {
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'lora-card'; // Reuse the same class for styling
|
card.className = 'lora-card'; // Reuse the same class for styling
|
||||||
@@ -123,153 +316,7 @@ export function createCheckpointCard(checkpoint) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Main card click event
|
// Add video auto-play on hover functionality if needed
|
||||||
card.addEventListener('click', () => {
|
|
||||||
// Show checkpoint details modal
|
|
||||||
const checkpointMeta = {
|
|
||||||
sha256: card.dataset.sha256,
|
|
||||||
file_path: card.dataset.filepath,
|
|
||||||
model_name: card.dataset.name,
|
|
||||||
file_name: card.dataset.file_name,
|
|
||||||
folder: card.dataset.folder,
|
|
||||||
modified: card.dataset.modified,
|
|
||||||
file_size: parseInt(card.dataset.file_size || '0'),
|
|
||||||
from_civitai: card.dataset.from_civitai === 'true',
|
|
||||||
base_model: card.dataset.base_model,
|
|
||||||
notes: card.dataset.notes || '',
|
|
||||||
preview_url: versionedPreviewUrl,
|
|
||||||
// Parse civitai metadata from the card's dataset
|
|
||||||
civitai: (() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(card.dataset.meta || '{}');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to parse civitai metadata:', e);
|
|
||||||
return {}; // Return empty object on error
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
tags: (() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(card.dataset.tags || '[]');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to parse tags:', e);
|
|
||||||
return []; // Return empty array on error
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
modelDescription: card.dataset.modelDescription || ''
|
|
||||||
};
|
|
||||||
showCheckpointModal(checkpointMeta);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle blur button functionality
|
|
||||||
const toggleBlurBtn = card.querySelector('.toggle-blur-btn');
|
|
||||||
if (toggleBlurBtn) {
|
|
||||||
toggleBlurBtn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const preview = card.querySelector('.card-preview');
|
|
||||||
const isBlurred = preview.classList.toggle('blurred');
|
|
||||||
const icon = toggleBlurBtn.querySelector('i');
|
|
||||||
|
|
||||||
// Update the icon based on blur state
|
|
||||||
if (isBlurred) {
|
|
||||||
icon.className = 'fas fa-eye';
|
|
||||||
} else {
|
|
||||||
icon.className = 'fas fa-eye-slash';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle the overlay visibility
|
|
||||||
const overlay = card.querySelector('.nsfw-overlay');
|
|
||||||
if (overlay) {
|
|
||||||
overlay.style.display = isBlurred ? 'flex' : 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show content button functionality
|
|
||||||
const showContentBtn = card.querySelector('.show-content-btn');
|
|
||||||
if (showContentBtn) {
|
|
||||||
showContentBtn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const preview = card.querySelector('.card-preview');
|
|
||||||
preview.classList.remove('blurred');
|
|
||||||
|
|
||||||
// Update the toggle button icon
|
|
||||||
const toggleBtn = card.querySelector('.toggle-blur-btn');
|
|
||||||
if (toggleBtn) {
|
|
||||||
toggleBtn.querySelector('i').className = 'fas fa-eye-slash';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the overlay
|
|
||||||
const overlay = card.querySelector('.nsfw-overlay');
|
|
||||||
if (overlay) {
|
|
||||||
overlay.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Favorite button click event
|
|
||||||
card.querySelector('.fa-star')?.addEventListener('click', async e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const starIcon = e.currentTarget;
|
|
||||||
const isFavorite = starIcon.classList.contains('fas');
|
|
||||||
const newFavoriteState = !isFavorite;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Save the new favorite state to the server
|
|
||||||
await saveModelMetadata(card.dataset.filepath, {
|
|
||||||
favorite: newFavoriteState
|
|
||||||
});
|
|
||||||
|
|
||||||
if (newFavoriteState) {
|
|
||||||
showToast('Added to favorites', 'success');
|
|
||||||
} else {
|
|
||||||
showToast('Removed from favorites', 'success');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to update favorite status:', error);
|
|
||||||
showToast('Failed to update favorite status', 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy button click event
|
|
||||||
card.querySelector('.fa-copy')?.addEventListener('click', async e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const checkpointName = card.dataset.file_name;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await copyToClipboard(checkpointName, 'Checkpoint name copied');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Copy failed:', err);
|
|
||||||
showToast('Copy failed', 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Civitai button click event
|
|
||||||
if (checkpoint.from_civitai) {
|
|
||||||
card.querySelector('.fa-globe')?.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
openCivitai(checkpoint.model_name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete button click event
|
|
||||||
card.querySelector('.fa-trash')?.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
showDeleteModal(checkpoint.file_path);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace preview button click event
|
|
||||||
card.querySelector('.fa-image')?.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
replaceCheckpointPreview(checkpoint.file_path);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open example images folder button click event
|
|
||||||
card.querySelector('.fa-folder-open')?.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
openExampleImagesFolder(checkpoint.sha256);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add autoplayOnHover handlers for video elements if needed
|
|
||||||
const videoElement = card.querySelector('video');
|
const videoElement = card.querySelector('video');
|
||||||
if (videoElement && autoplayOnHover) {
|
if (videoElement && autoplayOnHover) {
|
||||||
const cardPreview = card.querySelector('.card-preview');
|
const cardPreview = card.querySelector('.card-preview');
|
||||||
@@ -278,52 +325,10 @@ export function createCheckpointCard(checkpoint) {
|
|||||||
videoElement.removeAttribute('autoplay');
|
videoElement.removeAttribute('autoplay');
|
||||||
videoElement.pause();
|
videoElement.pause();
|
||||||
|
|
||||||
// Add mouse events to trigger play/pause
|
// Add mouse events to trigger play/pause using event attributes
|
||||||
cardPreview.addEventListener('mouseenter', () => {
|
cardPreview.setAttribute('onmouseenter', 'this.querySelector("video")?.play()');
|
||||||
videoElement.play();
|
cardPreview.setAttribute('onmouseleave', 'const v=this.querySelector("video"); if(v){v.pause();v.currentTime=0;}');
|
||||||
});
|
|
||||||
|
|
||||||
cardPreview.addEventListener('mouseleave', () => {
|
|
||||||
videoElement.pause();
|
|
||||||
videoElement.currentTime = 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
|
||||||
|
|
||||||
// These functions will be implemented in checkpointApi.js
|
|
||||||
function openCivitai(modelName) {
|
|
||||||
// Check if the global function exists (registered by PageControls)
|
|
||||||
if (window.openCivitai) {
|
|
||||||
window.openCivitai(modelName);
|
|
||||||
} else {
|
|
||||||
// Fallback implementation
|
|
||||||
const card = document.querySelector(`.lora-card[data-name="${modelName}"]`);
|
|
||||||
if (!card) return;
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceCheckpointPreview(filePath) {
|
|
||||||
if (window.replaceCheckpointPreview) {
|
|
||||||
window.replaceCheckpointPreview(filePath);
|
|
||||||
} else {
|
|
||||||
apiReplaceCheckpointPreview(filePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ function handleLoraCardEvent(event) {
|
|||||||
if (event.target.closest('.fa-globe')) {
|
if (event.target.closest('.fa-globe')) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (card.dataset.from_civitai === 'true') {
|
if (card.dataset.from_civitai === 'true') {
|
||||||
openCivitai(card.dataset.name);
|
openCivitai(card.dataset.filepath);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { VirtualScroller } from './VirtualScroller.js';
|
import { VirtualScroller } from './VirtualScroller.js';
|
||||||
import { createLoraCard, setupLoraCardEventDelegation } from '../components/LoraCard.js';
|
import { createLoraCard, setupLoraCardEventDelegation } from '../components/LoraCard.js';
|
||||||
import { createCheckpointCard } from '../components/CheckpointCard.js';
|
import { createCheckpointCard, setupCheckpointCardEventDelegation } from '../components/CheckpointCard.js';
|
||||||
import { fetchLorasPage } from '../api/loraApi.js';
|
import { fetchLorasPage } from '../api/loraApi.js';
|
||||||
import { fetchCheckpointsPage } from '../api/checkpointApi.js';
|
import { fetchCheckpointsPage } from '../api/checkpointApi.js';
|
||||||
import { showToast } from './uiHelpers.js';
|
import { showToast } from './uiHelpers.js';
|
||||||
@@ -68,6 +68,8 @@ export async function initializeInfiniteScroll(pageType = 'loras') {
|
|||||||
// Setup event delegation for lora cards if on the loras page
|
// Setup event delegation for lora cards if on the loras page
|
||||||
if (pageType === 'loras') {
|
if (pageType === 'loras') {
|
||||||
setupLoraCardEventDelegation();
|
setupLoraCardEventDelegation();
|
||||||
|
} else if (pageType === 'checkpoints') {
|
||||||
|
setupCheckpointCardEventDelegation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,16 +196,14 @@ function filterByFolder(folderPath) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openCivitai(modelName) {
|
export function openCivitai(filePath) {
|
||||||
// 从卡片的data-meta属性中获取civitai ID
|
const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
||||||
const loraCard = document.querySelector(`.lora-card[data-name="${modelName}"]`);
|
|
||||||
if (!loraCard) return;
|
if (!loraCard) return;
|
||||||
|
|
||||||
const metaData = JSON.parse(loraCard.dataset.meta);
|
const metaData = JSON.parse(loraCard.dataset.meta);
|
||||||
const civitaiId = metaData.modelId; // 使用modelId作为civitai模型ID
|
const civitaiId = metaData.modelId;
|
||||||
const versionId = metaData.id; // 使用id作为版本ID
|
const versionId = metaData.id;
|
||||||
|
|
||||||
// 构建URL
|
|
||||||
if (civitaiId) {
|
if (civitaiId) {
|
||||||
let url = `https://civitai.com/models/${civitaiId}`;
|
let url = `https://civitai.com/models/${civitaiId}`;
|
||||||
if (versionId) {
|
if (versionId) {
|
||||||
@@ -214,6 +212,7 @@ export function openCivitai(modelName) {
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
} else {
|
} else {
|
||||||
// 如果没有ID,尝试使用名称搜索
|
// 如果没有ID,尝试使用名称搜索
|
||||||
|
const modelName = loraCard.dataset.name;
|
||||||
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
|
window.open(`https://civitai.com/models?query=${encodeURIComponent(modelName)}`, '_blank');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user