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:
@@ -127,7 +127,11 @@ class TestEnrichHfMetadata:
|
||||
skill_name="enrich_hf_metadata",
|
||||
model_path="/p.safetensors",
|
||||
llm_output=llm,
|
||||
metadata={"base_model": "SD 1.5", "from_civitai": False},
|
||||
metadata={
|
||||
"base_model": "SD 1.5",
|
||||
"from_civitai": False,
|
||||
"hf_url": "https://huggingface.co/user/repo",
|
||||
},
|
||||
)
|
||||
applied = mock_apply.call_args[0][1]
|
||||
assert applied["base_model"] == "Flux.1 D"
|
||||
@@ -184,14 +188,17 @@ class TestEnrichHfMetadata:
|
||||
skill_name="enrich_hf_metadata",
|
||||
model_path="/p.safetensors",
|
||||
llm_output=llm,
|
||||
metadata={"from_civitai": False},
|
||||
metadata={
|
||||
"from_civitai": False,
|
||||
"hf_url": "https://huggingface.co/user/repo",
|
||||
},
|
||||
)
|
||||
applied = mock_apply.call_args[0][1]
|
||||
assert applied["civitai"]["description"] == "A short summary"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_short_description_skipped_for_civitai_model(self, processor):
|
||||
"""short_description NOT written for CivitAI models (has own description)."""
|
||||
async def test_short_description_skipped_without_hf_url(self, processor):
|
||||
"""short_description NOT written when the model has no HF source."""
|
||||
llm = {**self.MIN_LLM_OUTPUT, "short_description": "A short summary"}
|
||||
with (
|
||||
mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply,
|
||||
@@ -221,7 +228,10 @@ class TestEnrichHfMetadata:
|
||||
skill_name="enrich_hf_metadata",
|
||||
model_path="/p.safetensors",
|
||||
llm_output=self.MIN_LLM_OUTPUT,
|
||||
metadata={"from_civitai": False},
|
||||
metadata={
|
||||
"from_civitai": False,
|
||||
"hf_url": "https://huggingface.co/user/repo",
|
||||
},
|
||||
readme_content="# Hello\n\nThis is **bold**.",
|
||||
)
|
||||
applied = mock_apply.call_args[0][1]
|
||||
@@ -229,8 +239,8 @@ class TestEnrichHfMetadata:
|
||||
assert "<strong>bold</strong>" in applied.get("modelDescription", "")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_readme_content_skipped_for_civitai_model(self, processor):
|
||||
"""README content NOT converted for CivitAI models."""
|
||||
async def test_readme_content_skipped_without_hf_url(self, processor):
|
||||
"""README content NOT converted when the model has no HF source."""
|
||||
with (
|
||||
mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply,
|
||||
mock.patch("py.metadata_ops.download_preview", return_value=None),
|
||||
@@ -283,8 +293,31 @@ Content
|
||||
assert images[0]["meta"]["prompt"] == "a cat"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gallery_images_skipped_for_civitai_model(self, processor):
|
||||
"""Gallery images NOT extracted for CivitAI models."""
|
||||
async def test_gallery_images_skipped_without_hf_url(self, processor):
|
||||
"""Gallery images NOT extracted when the model has no HF source."""
|
||||
with (
|
||||
mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply,
|
||||
mock.patch("py.metadata_ops.download_preview", return_value=None),
|
||||
mock.patch("py.metadata_ops.refresh_cache"),
|
||||
):
|
||||
await processor.process(
|
||||
skill_name="enrich_hf_metadata",
|
||||
model_path="/p.safetensors",
|
||||
llm_output=self.MIN_LLM_OUTPUT,
|
||||
metadata={"from_civitai": True},
|
||||
readme_content="---\nwidget:\n- text: a\n output:\n url: x.png\n---\n",
|
||||
)
|
||||
applied = mock_apply.call_args[0][1]
|
||||
civitai = applied.get("civitai", {})
|
||||
assert "images" not in civitai
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gallery_images_extracted_for_civitai_linked_model(self, processor):
|
||||
"""A model may be on CivitAI and HuggingFace at once (#1094).
|
||||
|
||||
HF enrichment is gated on ``hf_url``, not on ``from_civitai``, so the
|
||||
README gallery is still applied when both sources are present.
|
||||
"""
|
||||
with (
|
||||
mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply,
|
||||
mock.patch("py.metadata_ops.download_preview", return_value=None),
|
||||
@@ -301,8 +334,11 @@ Content
|
||||
readme_content="---\nwidget:\n- text: a\n output:\n url: x.png\n---\n",
|
||||
)
|
||||
applied = mock_apply.call_args[0][1]
|
||||
civitai = applied.get("civitai", {})
|
||||
assert "images" not in civitai
|
||||
images = applied.get("civitai", {}).get("images", [])
|
||||
assert len(images) == 1
|
||||
assert images[0]["url"] == (
|
||||
"https://huggingface.co/user/repo/resolve/main/x.png"
|
||||
)
|
||||
|
||||
# -- tags ------------------------------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user