File size: 3,783 Bytes
728fc83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
utils/hf_utils.py
─────────────────
Utilities for validating and resolving HuggingFace model IDs or external URLs.
Used by the UI's "Custom model" text input before attempting to load.
"""

from __future__ import annotations

import logging
import re
from typing import Literal

import requests
from huggingface_hub import HfApi, model_info
from huggingface_hub.utils import RepositoryNotFoundError, HFValidationError

logger = logging.getLogger(__name__)

HF_REPO_RE = re.compile(r"^[A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+$")
URL_RE = re.compile(r"^https?://")

ModelSource = Literal["hf_repo", "url", "invalid"]


def classify_model_id(model_id: str) -> ModelSource:
    """
    Decide whether a raw string is:
      - a valid HuggingFace repo slug  ("org/model-name")
      - an external URL                ("https://…")
      - invalid
    """
    model_id = model_id.strip()
    if URL_RE.match(model_id):
        return "url"
    if HF_REPO_RE.match(model_id):
        return "hf_repo"
    return "invalid"


def validate_hf_repo(repo_id: str, token: str | None = None) -> tuple[bool, str]:
    """
    Check whether a HuggingFace repo exists and is publicly accessible.

    Returns
    -------
    (ok, message)
    """
    try:
        info = model_info(repo_id, token=token)
        pipeline_tag = getattr(info, "pipeline_tag", "unknown")
        return True, f"✅  Found: `{repo_id}` (pipeline: {pipeline_tag})"
    except RepositoryNotFoundError:
        return False, f"❌  Repository not found: `{repo_id}`"
    except HFValidationError as e:
        return False, f"❌  Validation error: {e}"
    except Exception as e:
        return False, f"❌  Unexpected error: {e}"


def validate_url(url: str, timeout: int = 5) -> tuple[bool, str]:
    """HEAD-request an external model URL to check reachability."""
    try:
        r = requests.head(url, timeout=timeout, allow_redirects=True)
        if r.status_code < 400:
            return True, f"✅  URL reachable (HTTP {r.status_code})"
        return False, f"❌  URL returned HTTP {r.status_code}"
    except requests.ConnectionError:
        return False, "❌  Cannot reach URL — check your connection"
    except requests.Timeout:
        return False, "❌  URL timed out"
    except Exception as e:
        return False, f"❌  {e}"


def validate_custom_model(model_id: str) -> tuple[bool, str]:
    """
    Top-level validator: routes to HF or URL check depending on input shape.
    Safe to call from Gradio event handlers.
    """
    model_id = model_id.strip()
    if not model_id:
        return False, "⚠️  No model ID entered"

    source = classify_model_id(model_id)
    if source == "hf_repo":
        return validate_hf_repo(model_id)
    if source == "url":
        return validate_url(model_id)
    return False, f"❌  `{model_id}` does not look like a HF repo ID or a URL"


def search_hf_models(query: str, pipeline_tag: str | None = None, limit: int = 10) -> list[dict]:
    """
    Search HuggingFace Hub for models matching a query.
    Returns a list of dicts with keys: model_id, pipeline_tag, downloads, likes.
    """
    try:
        api = HfApi()
        results = api.list_models(
            search=query,
            filter=pipeline_tag,
            sort="downloads",
            direction=-1,
            limit=limit,
        )
        out = []
        for m in results:
            out.append({
                "model_id": m.id,
                "pipeline_tag": getattr(m, "pipeline_tag", "—"),
                "downloads": getattr(m, "downloads", 0),
                "likes": getattr(m, "likes", 0),
            })
        return out
    except Exception as e:
        logger.warning("HF search failed: %s", e)
        return []