feat: model compatibility warning on HF lookup
- GET /api/models/lookup now returns compatible: bool and warning: str|null - compatible=false + warning when pipeline_tag is absent (no task tag on HF) or present but not in the supported adapter map - Warning message names the unsupported pipeline_tag and lists supported types - ModelsView: yellow compat-warning banner below preview description; Add button relabels to "Add anyway" with muted styling when incompatible - test_models: accept 405 for path-traversal DELETE tests (StaticFiles mount returns 405 for non-GET methods when web/dist exists)
This commit is contained in:
parent
49ec85706c
commit
ce12b29c94
3 changed files with 61 additions and 6 deletions
|
|
@ -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 [],
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -54,12 +54,18 @@
|
|||
{{ lookupResult.description }}
|
||||
</p>
|
||||
|
||||
<div v-if="lookupResult.warning" class="compat-warning" role="alert">
|
||||
<span class="compat-warning-icon">⚠️</span>
|
||||
<span>{{ lookupResult.warning }}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn-primary btn-add-queue"
|
||||
:class="{ 'btn-add-queue-warn': !lookupResult.compatible }"
|
||||
:disabled="lookupResult.already_installed || lookupResult.already_queued || addingToQueue"
|
||||
@click="addToQueue"
|
||||
>
|
||||
{{ addingToQueue ? 'Adding…' : 'Add to queue' }}
|
||||
{{ addingToQueue ? 'Adding…' : lookupResult.compatible ? 'Add to queue' : 'Add anyway' }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -188,6 +194,8 @@ interface LookupResult {
|
|||
repo_id: string
|
||||
pipeline_tag: string | null
|
||||
adapter_recommendation: string | null
|
||||
compatible: boolean
|
||||
warning: string | null
|
||||
size: number | null
|
||||
description: string | null
|
||||
already_installed: boolean
|
||||
|
|
@ -565,10 +573,34 @@ onUnmounted(() => {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compat-warning {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: var(--radius-sm, 0.25rem);
|
||||
background: color-mix(in srgb, var(--color-warning, #f59e0b) 12%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-warning, #f59e0b) 40%, transparent);
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-text, #1a2338);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.compat-warning-icon {
|
||||
flex-shrink: 0;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.btn-add-queue {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn-add-queue-warn {
|
||||
background: var(--color-surface-raised, #e4ebf5);
|
||||
color: var(--color-text-secondary, #6b7a99);
|
||||
border: 1px solid var(--color-border, #d0d7e8);
|
||||
}
|
||||
|
||||
/* ── Model cards (queue + downloads) ── */
|
||||
.model-card {
|
||||
border: 1px solid var(--color-border, #a8b8d0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue