diff --git a/app/models.py b/app/models.py index 0ac40a8..4a6ffcd 100644 --- a/app/models.py +++ b/app/models.py @@ -200,8 +200,26 @@ def lookup_model(repo_id: str) -> dict: data = resp.json() pipeline_tag = data.get("pipeline_tag") adapter_recommendation = _TAG_TO_ADAPTER.get(pipeline_tag) if pipeline_tag else None - if pipeline_tag and adapter_recommendation is None: - logger.warning("Unknown pipeline_tag %r for %s — no adapter recommendation", pipeline_tag, repo_id) + + # Determine compatibility and surface a human-readable warning + _supported = ", ".join(sorted(_TAG_TO_ADAPTER.keys())) + if adapter_recommendation is not None: + compatible = True + warning: str | None = None + elif pipeline_tag is None: + compatible = False + warning = ( + "This model has no task tag on HuggingFace — adapter type is unknown. " + "It may not work with Avocet's email classification pipeline." + ) + logger.warning("No pipeline_tag for %s — no adapter recommendation", repo_id) + else: + compatible = False + warning = ( + f"\"{pipeline_tag}\" models are not supported by Avocet's email classification adapters. " + f"Supported task types: {_supported}." + ) + logger.warning("Unsupported pipeline_tag %r for %s", pipeline_tag, repo_id) # Estimate model size from siblings list siblings = data.get("siblings") or [] @@ -216,6 +234,8 @@ def lookup_model(repo_id: str) -> dict: "repo_id": repo_id, "pipeline_tag": pipeline_tag, "adapter_recommendation": adapter_recommendation, + "compatible": compatible, + "warning": warning, "model_size_bytes": model_size_bytes, "description": description, "tags": data.get("tags") or [], diff --git a/tests/test_models.py b/tests/test_models.py index 0e31f04..d73e845 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -371,15 +371,18 @@ def test_delete_installed_not_found_returns_404(client): def test_delete_installed_path_traversal_blocked(client): - """DELETE /installed/../../etc must be blocked (400 or 422).""" + """DELETE /installed/../../etc must be blocked. + Path traversal normalises to a different URL (/api/etc); if web/dist exists + the StaticFiles mount intercepts it and returns 405 (GET/HEAD only). + """ r = client.delete("/api/models/installed/../../etc") - assert r.status_code in (400, 404, 422) + assert r.status_code in (400, 404, 405, 422) def test_delete_installed_dotdot_name_blocked(client): """A name containing '..' in any form must be rejected.""" r = client.delete("/api/models/installed/..%2F..%2Fetc") - assert r.status_code in (400, 404, 422) + assert r.status_code in (400, 404, 405, 422) def test_delete_installed_name_with_slash_blocked(client): diff --git a/web/src/views/ModelsView.vue b/web/src/views/ModelsView.vue index 10df382..3d7871b 100644 --- a/web/src/views/ModelsView.vue +++ b/web/src/views/ModelsView.vue @@ -54,12 +54,18 @@ {{ lookupResult.description }}
+