mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 03:01:27 -03:00
fix(links): let CivitAI and HuggingFace links coexist (#1094)
A model could have CivitAI metadata and a HuggingFace link at the same time, but only one of the two "View on ..." entries ever rendered, because both the model modal and the card globe asked the `from_civitai` provenance flag which source to show. `set_hf_url` wrote `false` and a CivitAI refresh wrote `true`, so whichever ran last erased the other: linking HF hid "View on CivitAI" even though the civitai payload was still in the sidecar, and (on the card) a later refresh pointed the single globe icon back at CivitAI, hiding the HF entry. Decide the links from the data itself instead: - `set_hf_url` no longer touches `from_civitai`; it records where the metadata came from, and HF provenance is already tracked by `hf_url`. - Add `hasCivitaiSource(civitai)` in the shared card/modal utils and gate the modal's CivitAI link, the card globe (title, enabled state, click target, new `data-has_civitai`) and the context-menu `civitai` action on actual CivitAI data (`modelId` / `model_id` / `id`). A dual-source model now shows both links, and a CivitAI-only model with no `hf_url` stays as before. - Agent HF enrichment (`PostProcessor.is_hf_model`) keyed off `not from_civitai`, which stopped being a synonym for "has an HF source" once both sources can coexist (and already broke after a CivitAI refresh flipped the flag back to true). Key it off `hf_url` directly; the post-processor tests move to that discriminator and gain a dual-source case. Regression tests: the set-hf-url handler preserves civitai + `from_civitai` and no longer forces the flag false, the modal renders both links (including with `from_civitai: false`), and the card globe targets/opens the right source and is disabled when neither is available. Backend: 2749 passed. Frontend: 1098 JS + 91 Vue tests passed.
This commit is contained in:
@@ -446,7 +446,10 @@ export const ModelContextMenuMixin = {
|
||||
this.downloadExampleImages(true);
|
||||
return true;
|
||||
case 'civitai':
|
||||
if (this.currentCard.dataset.from_civitai === 'true') {
|
||||
// Gate on actual CivitAI data (not the `from_civitai` flag) so
|
||||
// that linking HuggingFace does not make the model look like it
|
||||
// has no CivitAI info (#1094).
|
||||
if (this.currentCard.dataset.has_civitai === 'true') {
|
||||
if (this.currentCard.querySelector('.fa-globe')) {
|
||||
this.currentCard.querySelector('.fa-globe').click();
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { showToast, openCivitai, openHuggingFace, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, sendEmbeddingToWorkflow, openExampleImagesFolder, buildLoraSyntax, sendModelPathToWorkflow } from '../../utils/uiHelpers.js';
|
||||
import { state, getCurrentPageState } from '../../state/index.js';
|
||||
import { showModelModal } from './ModelModal.js';
|
||||
import { hasCivitaiSource } from './utils.js';
|
||||
import { bulkManager } from '../../managers/BulkManager.js';
|
||||
import { modalManager } from '../../managers/ModalManager.js';
|
||||
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, getMatureBlurThreshold, MODEL_SUBTYPE_DISPLAY_NAMES, MODEL_CARD_DRAG_MIME_TYPE } from '../../utils/constants.js';
|
||||
@@ -63,7 +64,10 @@ function handleModelCardEvent_internal(event, modelType) {
|
||||
|
||||
if (event.target.closest('.fa-globe')) {
|
||||
event.stopPropagation();
|
||||
if (card.dataset.from_civitai === 'true') {
|
||||
// CivitAI wins when the model actually has CivitAI data; otherwise fall
|
||||
// back to HuggingFace. Relying on `from_civitai` here made the two
|
||||
// sources mutually exclusive whenever one of them was (re)linked (#1094).
|
||||
if (card.dataset.has_civitai === 'true') {
|
||||
openCivitai(card.dataset.filepath);
|
||||
} else if (card.dataset.hf_url) {
|
||||
openHuggingFace(card.dataset.hf_url);
|
||||
@@ -478,6 +482,9 @@ export function createModelCard(model, modelType) {
|
||||
card.dataset.modified = model.modified;
|
||||
card.dataset.file_size = model.file_size;
|
||||
card.dataset.from_civitai = model.from_civitai;
|
||||
// Independent of `from_civitai`: a model can have both CivitAI data and an
|
||||
// HF link, and the card globe must keep pointing at CivitAI when it does.
|
||||
card.dataset.has_civitai = hasCivitaiSource(model.civitai) ? 'true' : 'false';
|
||||
card.dataset.usage_count = String(model.usage_count);
|
||||
card.dataset.notes = model.notes || '';
|
||||
card.dataset.base_model = model.base_model || 'Unknown';
|
||||
@@ -600,12 +607,13 @@ export function createModelCard(model, modelType) {
|
||||
const favoriteTitle = isFavorite ?
|
||||
translate('modelCard.actions.removeFromFavorites', {}, 'Remove from favorites') :
|
||||
translate('modelCard.actions.addToFavorites', {}, 'Add to favorites');
|
||||
const globeTitle = model.from_civitai ?
|
||||
const hasCivitai = hasCivitaiSource(model.civitai);
|
||||
const globeTitle = hasCivitai ?
|
||||
translate('modelCard.actions.viewOnCivitai', {}, 'View on Civitai') :
|
||||
model.hf_url ?
|
||||
translate('modelCard.actions.viewOnHuggingFace', {}, 'View on Hugging Face') :
|
||||
translate('modelCard.actions.notAvailableFromCivitai', {}, 'Not available from Civitai');
|
||||
const globeEnabled = model.from_civitai || !!model.hf_url;
|
||||
const globeEnabled = hasCivitai || !!model.hf_url;
|
||||
let sendTitle;
|
||||
let copyTitle;
|
||||
if (modelType === MODEL_TYPES.LORA) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from './ModelMetadata.js';
|
||||
import { setupTagEditMode } from './ModelTags.js';
|
||||
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
||||
import { renderCompactTags, setupTagTooltip, formatFileSize, escapeAttribute, escapeHtml } from './utils.js';
|
||||
import { renderCompactTags, setupTagTooltip, formatFileSize, escapeAttribute, escapeHtml, hasCivitaiSource } from './utils.js';
|
||||
import { renderTriggerWords, setupTriggerWordsEditMode } from './TriggerWords.js';
|
||||
import { parsePresets, renderPresetTags } from './PresetTags.js';
|
||||
import { initVersionsTab } from './ModelVersionsTab.js';
|
||||
@@ -389,7 +389,11 @@ export async function showModelModal(model, modelType) {
|
||||
const licenseIcons = useNewIcons
|
||||
? renderNewLicenseIcons(modelWithFullData)
|
||||
: renderLicenseIcons(modelWithFullData);
|
||||
const viewOnCivitaiAction = modelWithFullData.from_civitai ? `
|
||||
// Gate the CivitAI link on actual CivitAI data, not the `from_civitai`
|
||||
// provenance flag: a model can be linked to HuggingFace and to CivitAI at
|
||||
// the same time, and both links must coexist (#1094).
|
||||
const hasCivitai = hasCivitaiSource(modelWithFullData.civitai);
|
||||
const viewOnCivitaiAction = hasCivitai ? `
|
||||
<div class="civitai-view" title="${translate('modals.model.actions.viewOnCivitai', {}, 'View on Civitai')}" data-action="view-civitai" data-filepath="${escapedFilePathAttr}">
|
||||
<i class="fas fa-globe"></i> ${translate('modals.model.actions.viewOnCivitaiText', {}, 'View on Civitai')}
|
||||
</div>`.trim() : '';
|
||||
|
||||
@@ -36,6 +36,24 @@ export function formatFileSize(bytes) {
|
||||
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a model has usable CivitAI metadata to link to.
|
||||
*
|
||||
* CivitAI links must be gated on the presence of actual CivitAI data rather
|
||||
* than the `from_civitai` provenance flag: linking a model to HuggingFace used
|
||||
* to flip `from_civitai` to false, which hid the CivitAI link even though the
|
||||
* model still had CivitAI metadata. See issue #1094.
|
||||
*
|
||||
* @param {Object} [civitaiData] - The model's `civitai` payload
|
||||
* @returns {boolean} True when a CivitAI model/version id is available
|
||||
*/
|
||||
export function hasCivitaiSource(civitaiData) {
|
||||
if (!civitaiData || typeof civitaiData !== 'object') return false;
|
||||
return Boolean(
|
||||
civitaiData.modelId ?? civitaiData.model_id ?? civitaiData.id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render compact tags
|
||||
* @param {Array} tags - Array of tags
|
||||
|
||||
Reference in New Issue
Block a user