Spaces:
Running on Zero
Running on Zero
Download app.py from AxionLab-official/supra-worldcup-model-submission: direct link, hf CLI and curl.
- Browser
- Download file 67.3 kB
-
https://huggingface.co/spaces/AxionLab-official/supra-worldcup-model-submission/resolve/main/app.py
- Command line
-
hf download hf://spaces/AxionLab-official/supra-worldcup-model-submission/app.py
-
curl -L -o app.py https://huggingface.co/spaces/AxionLab-official/supra-worldcup-model-submission/resolve/main/app.py
67.3 kB
| """ | |
| SupraLabs Open Source Model Cup — Registration Portal. | |
| This Space is NOT the tournament itself. It is the official entry | |
| portal: it explains the rules, lets organizations register a model, | |
| validates submissions locally, and shows the real list of registered | |
| models — persisted to a Hugging Face Dataset repo, not mocked. The | |
| tournament (matches, standings, brackets) is a separate Space. | |
| REQUIRED SETUP (see the SERVICES section below for full detail): | |
| 1. Create a Dataset repo on huggingface.co (it MUST be private: | |
| it stores contact emails and, optionally, endpoint API keys), | |
| e.g. SupraLabs/model-cup-registrations. | |
| 2. Add a Space secret named HF_TOKEN with an HF access token that | |
| has WRITE permission on that dataset. Settings -> Variables | |
| and secrets -> New secret. Never hardcode a token in this file. | |
| 3. If your dataset repo id differs from the default below, set a | |
| second Space secret/variable named HF_DATASET_REPO. | |
| Without that setup (e.g. running this file locally), registrations | |
| still work end-to-end against a local ./data/registrations.jsonl | |
| fallback file, so development doesn't require any HF credentials. | |
| Everything lives in this single file for simple deployment, but is | |
| organized into clearly separated sections mirroring a normal package | |
| layout, so it can be split back into files later with minimal changes: | |
| 1. DATA -> static rules/FAQ/roadmap content | |
| 2. VALIDATORS -> pure functions, no side effects, fully testable | |
| 3. SERVICES -> HF Dataset read/write layer (real persistence) | |
| 4. MODELS -> typed registration record shape | |
| 5. COMPONENTS -> reusable HTML fragment builders | |
| 6. FORMS -> registration form submit handler | |
| 7. PAGES -> one render function per screen | |
| 8. APP -> Gradio wiring / navigation | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import os | |
| import random | |
| import re | |
| import threading | |
| import time | |
| from dataclasses import asdict, dataclass | |
| from html import escape | |
| from pathlib import Path | |
| import gradio as gr | |
| logger = logging.getLogger("model_cup") | |
| # ZeroGPU (the free Gradio hardware tier on HF Spaces) refuses to start | |
| # a Space unless at least one function is decorated with @spaces.GPU. | |
| # This app does no local GPU work (no model inference happens here — | |
| # it's a registration form), so this is a harmless no-op: the | |
| # decorator is only ever declared, never actually invoked. | |
| # | |
| # The import is wrapped in a broad except (not just ImportError): the | |
| # `spaces` package talks to HF's GPU scheduling infrastructure at | |
| # import time, and if that infra hiccups, an unhandled exception here | |
| # would take down the whole app before Gradio even starts — for a form | |
| # that never touches a GPU. Falling back to the dummy decorator keeps | |
| # the registration portal itself resilient to that failure mode, | |
| # independent of whatever HF's container/GPU allocation is doing. | |
| try: | |
| import spaces | |
| except Exception: | |
| class spaces: # type: ignore | |
| def GPU(func=None, **kwargs): | |
| def wrapper(f): | |
| return f | |
| return wrapper(func) if func else wrapper | |
| def _zerogpu_startup_probe(): | |
| """Unused placeholder so HF Spaces' ZeroGPU startup check passes. | |
| This app never runs model inference and never calls this function.""" | |
| pass | |
| # ===================================================================== | |
| # 1. DATA — static content (rules/FAQ/roadmap) + local fallback dir. | |
| # Registered models are NOT mocked here — they live in a real HF | |
| # Dataset repo (see SERVICES section below). This directory is only | |
| # used as an offline fallback when no HF token is configured. | |
| # ===================================================================== | |
| DATA_DIR = Path(__file__).resolve().parent / "data" | |
| DATA_DIR.mkdir(exist_ok=True) | |
| # Weight classes, boxing-style. Each entry is (name, min_params | |
| # inclusive, max_params exclusive or None for "and above"). Order | |
| # matters — categorize_by_parameters (below) walks this top-to-bottom, | |
| # and every page that lists categories (Rules, Tournament Structure, | |
| # FAQ) is generated from this single table so they can't drift apart. | |
| # | |
| # The last class's max is also the hard size limit for the whole | |
| # tournament (see MAX_PARAMETERS): a model above it has no class to | |
| # play in, so registration rejects it. Set it to None for an open top. | |
| WEIGHT_CLASSES = [ | |
| ("Atomweight", 100_000, 10_000_000), | |
| ("Featherweight", 10_000_000, 1_000_000_000), | |
| ("Lightweight", 1_000_000_000, 7_000_000_000), | |
| ] | |
| WEIGHT_CLASS_NAMES = [name for name, *_ in WEIGHT_CLASSES] | |
| MAX_PARAMETERS: int | None = WEIGHT_CLASSES[-1][2] # inclusive ceiling, None = no limit | |
| _NUMBER_WORDS = {1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven"} | |
| def _join_names(names: list[str], conjunction: str = "or") -> str: | |
| """['A', 'B', 'C'] -> 'A, B, or C' (or 'A, B, and C' with conjunction='and').""" | |
| if len(names) < 3: | |
| return f" {conjunction} ".join(names) | |
| return f"{', '.join(names[:-1])}, {conjunction} {names[-1]}" | |
| def _format_param_count(n: int) -> str: | |
| """Human-friendly shorthand for a parameter count, e.g. 1_000_000_000 -> '1B'.""" | |
| if n >= 1_000_000_000: | |
| v = n / 1_000_000_000 | |
| return f"{v:.0f}B" if v == int(v) else f"{v:.1f}B" | |
| if n >= 1_000_000: | |
| v = n / 1_000_000 | |
| return f"{v:.0f}M" if v == int(v) else f"{v:.1f}M" | |
| if n >= 1_000: | |
| v = n / 1_000 | |
| return f"{v:.0f}K" if v == int(v) else f"{v:.1f}K" | |
| return str(n) | |
| def _weight_class_range_label(min_params: int, max_params: int | None) -> str: | |
| low = _format_param_count(min_params) | |
| if max_params is None: | |
| return f"{low}+ parameters" | |
| high = _format_param_count(max_params) | |
| return f"{low}\u2013{high} parameters" | |
| MAX_PARAMETERS_LABEL = _format_param_count(MAX_PARAMETERS) if MAX_PARAMETERS else None | |
| STATUS_COLORS = { | |
| "Verified": "#2f9e68", | |
| "Pending Review": "#b8860b", | |
| "Rejected": "#c8404c", | |
| "Offline": "#7a828c", | |
| } | |
| MODEL_TYPES = ["Base", "Instruct", "Reasoning", "Code", "Multimodal", "Other"] | |
| PROVIDERS = ["Hugging Face Inference", "OpenRouter", "Together", "Groq", "Fireworks", "Organization Endpoint", "Other"] | |
| YES_NO = ["Yes", "No"] | |
| ALL_STATUSES = ["Pending Review", "Verified", "Rejected", "Offline"] | |
| _SIZE_LIMIT_RULES = ( | |
| [f"Models with more than {MAX_PARAMETERS_LABEL} parameters are not eligible: " | |
| f"the heaviest weight class tops out at {MAX_PARAMETERS_LABEL}."] | |
| if MAX_PARAMETERS_LABEL else [] | |
| ) | |
| RULES_SECTIONS = [ | |
| ("General Rules", [ | |
| "Every model competing in the tournament must be registered through this portal before the registration deadline.", | |
| "One organization may register multiple models — each is evaluated and scored independently.", | |
| "Each Hugging Face repository can be registered once. A rejected registration may be resubmitted.", | |
| "By registering, an organization agrees to keep its inference endpoint available for the duration of the tournament.", | |
| "Every registration is reviewed by the organizers and stays Pending Review until it is approved.", | |
| ]), | |
| ("Eligibility", [ | |
| "The model must be open source, with weights or a clearly documented open license.", | |
| "The model must be publicly accessible — no invite-only or gated access without a public request path.", | |
| "The model must have a valid Hugging Face repository (or equivalent public model card).", | |
| "Closed-source, fully proprietary models are not eligible for this tournament (see FAQ).", | |
| *_SIZE_LIMIT_RULES, | |
| "The reported parameter count is checked by the organizers during review, and a model is only approved " | |
| "once it matches.", | |
| ]), | |
| ("Inference Requirements", [ | |
| "The model must provide a stable inference method that organizers can call automatically, without manual intervention.", | |
| "Accepted inference providers include: Hugging Face Inference, a Dedicated Endpoint, an Organization API, " | |
| "OpenRouter, Together AI, Groq, Fireworks, or any other stable provider with comparable uptime guarantees.", | |
| "If the endpoint requires authentication, provide a dedicated, revocable API key (ideally with a spending " | |
| "limit) in the registration form. It is stored privately and is never shown publicly.", | |
| "The organizers must be able to execute benchmark questions against the endpoint automatically and reliably.", | |
| "Organizations are responsible for keeping their endpoint working throughout the competition.", | |
| ]), | |
| ("Tournament Rules", [ | |
| f"Models are automatically assigned to a weight class ({_join_names(WEIGHT_CLASS_NAMES)}) " | |
| "based on parameter count.", | |
| "Every eligible model competes in both its own weight category and the Total World Cup.", | |
| "Match outcomes are decided by an automated judge scoring each model's answers to a shared question set.", | |
| "Group standings follow standard football rules: 3 points for a win, 1 for a draw, 0 for a loss.", | |
| ]), | |
| ("Disqualification", [ | |
| "If a model's inference becomes unavailable during the tournament, organizers may temporarily disable it " | |
| "until the issue is resolved, without eliminating it from the bracket.", | |
| "Organizers reserve the right to reject malicious, broken, or clearly non-functional submissions at any stage.", | |
| "Repeated, unresolved endpoint outages during scheduled matches may result in forfeiture of those matches.", | |
| ]), | |
| ("Categories", [ | |
| f"{name} — {_weight_class_range_label(min_p, max_p)}." | |
| for name, min_p, max_p in WEIGHT_CLASSES | |
| ] + [ | |
| "Total World Cup — every eligible model, from the lightest class to the heaviest, competing together.", | |
| ]), | |
| ("Sportsmanship", [ | |
| "Organizations should submit accurate model information — misrepresenting parameter count, license, or " | |
| "capabilities to gain a category advantage is grounds for disqualification.", | |
| "Attempts to game or exploit the automated judge are treated as a rules violation.", | |
| ]), | |
| ("Updates", [ | |
| "Rules may be updated between seasons. Registered organizations will be notified of any material changes " | |
| "before the next tournament begins.", | |
| ]), | |
| ] | |
| FAQ_ITEMS = [ | |
| ("Can I register multiple models?", | |
| "Yes. One organization can register as many models as it wants — each is registered, validated, and scored " | |
| "independently. The same Hugging Face repository can only be registered once."), | |
| ("How are categories determined?", | |
| f"Automatically, based on parameter count, using {_NUMBER_WORDS[len(WEIGHT_CLASSES)]} boxing-style " | |
| f"weight classes: {_join_names(WEIGHT_CLASS_NAMES, 'and')} (see Tournament Structure for the exact " | |
| "ranges). Every model also competes in the Total World Cup."), | |
| ("What is the maximum model size?", | |
| (f"{MAX_PARAMETERS_LABEL} parameters. Larger models have no weight class to compete in, so they can't be registered." | |
| if MAX_PARAMETERS_LABEL else "There is no size limit.")), | |
| ("Do you verify the parameter count?", | |
| "Yes. The organizers review every registration, including the reported parameter count, before approving it."), | |
| ("My endpoint needs an API key. What should I do?", | |
| "Add it in the optional API key field of the registration form. Use a dedicated key that you can revoke, " | |
| "ideally with a spending limit. It is stored privately and never shown on the public list."), | |
| ("Can I update my endpoint?", | |
| "Yes — contact the organizers with your registration details and the updated endpoint. Self-service editing isn't available in this PoC yet (see Roadmap)."), | |
| ("Can closed-source models participate?", | |
| "No. This tournament is specifically for open-source models with publicly accessible weights or documentation."), | |
| ("What happens if my endpoint goes offline?", | |
| "Organizers may temporarily disable your model until the issue is resolved. Repeated unresolved outages during scheduled matches may result in forfeiture of those matches."), | |
| ("How are winners determined?", | |
| "Each match is a set of benchmark questions answered by both competing models, scored by an automated judge. Group stage follows standard football points (3/1/0); the knockout stage decides the champion of each cup."), | |
| ] | |
| ROADMAP_ITEMS = [ | |
| ("Season Rankings", "Persistent rankings carried across multiple tournament seasons."), | |
| ("Live Matches", "Real-time match progress as benchmark questions are answered."), | |
| ("Automatic Registration Verification", "Automated checks that ping the submitted endpoint and validate the HF repo before marking a model Verified."), | |
| ("Leaderboard", "A global leaderboard across all categories and seasons."), | |
| ("Organization Profiles", "A public profile page per organization with all their registered models."), | |
| ("Historical Seasons", "An archive of past tournaments, brackets, and results."), | |
| ("Model Statistics", "Deeper per-model analytics: response time, category-specific performance, judge score breakdowns."), | |
| ("Tournament API", "A public read API so third parties can build their own dashboards on top of tournament data."), | |
| ] | |
| TOURNAMENT_STRUCTURE = [ | |
| (f"{name} Cup", f"For models with {_weight_class_range_label(min_p, max_p)}.") | |
| for name, min_p, max_p in WEIGHT_CLASSES | |
| ] + [ | |
| ("Total World Cup", "Every eligible model, across all weight classes, competing together for one trophy."), | |
| ] | |
| # ===================================================================== | |
| # 2. VALIDATORS — pure functions, no side effects | |
| # ===================================================================== | |
| FIELD_LABELS = { | |
| "organization_name": "Organization Name", | |
| "organization_website": "Organization Website", | |
| "organization_email": "Organization Email", | |
| "contact_person": "Contact Person", | |
| "model_name": "Model Name", | |
| "model_hf_url": "Model Hugging Face URL", | |
| "license": "License", | |
| "parameters_raw": "Number of Parameters", | |
| "inference_endpoint": "Inference Endpoint", | |
| "api_docs_url": "API Documentation URL", | |
| "endpoint_api_key": "Endpoint API Key", | |
| "languages": "Languages", | |
| "tags": "Tags", | |
| "short_description": "Short Description", | |
| "long_description": "Long Description", | |
| } | |
| REQUIRED_FIELDS = ( | |
| "organization_name", "organization_website", "organization_email", "contact_person", | |
| "model_name", "model_hf_url", "license", "parameters_raw", "inference_endpoint", | |
| "short_description", | |
| ) | |
| # Basic anti-abuse limits: the registration list is public, so unbounded | |
| # text would let anyone flood the page. | |
| MAX_SHORT_TEXT = 300 | |
| MAX_LONG_TEXT = 3000 | |
| def validate_required(value: str) -> bool: | |
| return bool(value and value.strip()) | |
| def validate_email(email: str) -> bool: | |
| return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", (email or "").strip())) | |
| def validate_url(url: str) -> bool: | |
| return bool(re.match(r"^https?://[^\s]+\.[^\s]+", (url or "").strip())) | |
| def validate_hf_repo_url(url: str) -> bool: | |
| """Accepts a full HF URL (https://huggingface.co/org/model) or an org/model shorthand.""" | |
| url = (url or "").strip() | |
| if re.match(r"^https?://huggingface\.co/[\w\-.]+/[\w\-.]+/?$", url): | |
| return True | |
| if re.match(r"^[\w\-.]+/[\w\-.]+$", url): | |
| return True | |
| return False | |
| def normalize_hf_repo(url: str) -> str: | |
| """Turns a full HF URL into the org/model shorthand used as hf_repo.""" | |
| url = (url or "").strip().rstrip("/") | |
| match = re.match(r"^https?://huggingface\.co/([\w\-.]+/[\w\-.]+)$", url) | |
| return match.group(1) if match else url | |
| def parse_parameters(raw: str) -> int | None: | |
| """Accepts plain integers (67000000) or shorthand with K/M/B suffix (67M, 1.3B).""" | |
| if raw is None: | |
| return None | |
| s = str(raw).strip().replace(",", "").replace(" ", "") | |
| if not s: | |
| return None | |
| m = re.match(r"^(\d+(\.\d+)?)([kKmMbB]?)$", s) | |
| if not m: | |
| return None | |
| number = float(m.group(1)) | |
| suffix = m.group(3).upper() | |
| multiplier = {"K": 1_000, "M": 1_000_000, "B": 1_000_000_000, "": 1}[suffix] | |
| value = int(number * multiplier) | |
| return value if value > 0 else None | |
| def categorize_by_parameters(parameters: int) -> str: | |
| """Maps a parameter count to a weight class name using WEIGHT_CLASSES. | |
| Walks the classes lightest-first and returns the first one whose max | |
| isn't exceeded, so anything below the first class's floor lands in | |
| the lightest class, and anything at/above the last class's max lands | |
| in the heaviest (registration rejects those above MAX_PARAMETERS | |
| before this is ever called).""" | |
| for name, _min_params, max_params in WEIGHT_CLASSES: | |
| if max_params is None or parameters < max_params: | |
| return name | |
| return WEIGHT_CLASSES[-1][0] | |
| def validate_registration(fields: dict) -> list[str]: | |
| """ | |
| Validates the full registration form. | |
| Returns a list of human-readable error messages (empty list = valid). | |
| """ | |
| errors = [] | |
| for key in REQUIRED_FIELDS: | |
| if not validate_required(fields.get(key, "")): | |
| errors.append(f"'{FIELD_LABELS[key]}' is required.") | |
| for key, label in FIELD_LABELS.items(): | |
| limit = MAX_LONG_TEXT if key == "long_description" else MAX_SHORT_TEXT | |
| if len(str(fields.get(key) or "").strip()) > limit: | |
| errors.append(f"'{label}' is too long (max {limit} characters).") | |
| # Only run format checks on fields that were actually filled in, | |
| # to avoid duplicate noise on top of the required-field errors. | |
| if validate_required(fields.get("organization_website", "")) and not validate_url(fields["organization_website"]): | |
| errors.append("'Organization Website' must be a valid URL (starting with http:// or https://).") | |
| if validate_required(fields.get("organization_email", "")) and not validate_email(fields["organization_email"]): | |
| errors.append("'Organization Email' must be a valid email address.") | |
| if validate_required(fields.get("model_hf_url", "")) and not validate_hf_repo_url(fields["model_hf_url"]): | |
| errors.append("'Model Hugging Face URL' must be a valid Hugging Face repository (e.g. https://huggingface.co/org/model).") | |
| if validate_required(fields.get("inference_endpoint", "")) and not validate_url(fields["inference_endpoint"]): | |
| errors.append("'Inference Endpoint' must be a valid URL.") | |
| api_docs = fields.get("api_docs_url", "") | |
| if validate_required(api_docs) and not validate_url(api_docs): | |
| errors.append("'API Documentation URL' must be a valid URL if provided.") | |
| if validate_required(fields.get("parameters_raw", "")): | |
| parameters = parse_parameters(fields["parameters_raw"]) | |
| if parameters is None: | |
| errors.append("'Number of Parameters' must be a positive number (e.g. 67000000, 67M, 1.3B).") | |
| elif MAX_PARAMETERS is not None and parameters > MAX_PARAMETERS: | |
| errors.append( | |
| f"'Number of Parameters' is above the {MAX_PARAMETERS_LABEL} limit — " | |
| f"the heaviest weight class tops out at {MAX_PARAMETERS_LABEL}." | |
| ) | |
| if not fields.get("model_type"): | |
| errors.append("'Model Type' is required.") | |
| if not fields.get("inference_provider"): | |
| errors.append("'Inference Provider' is required.") | |
| return errors | |
| # ===================================================================== | |
| # 3. SERVICES — registered models are stored as JSONL in a real HF | |
| # Dataset repo (not a mock). This is the swap-in point if you ever | |
| # move to a real database later — everything else in the app only | |
| # calls get_registered_models() / add_registered_model() below. | |
| # | |
| # Setup required on the Space (see the docstring at the top of this | |
| # file for the full explanation): | |
| # 1. Create a PRIVATE Dataset repo on huggingface.co, e.g. | |
| # SupraLabs/model-cup-registrations. | |
| # 2. Add an HF token with WRITE access as a Space *secret* named | |
| # HF_TOKEN (Settings -> Variables and secrets -> New secret). | |
| # Never hardcode a token in this file. | |
| # 3. Set HF_DATASET_REPO below (or via the HF_DATASET_REPO env | |
| # var) to that repo's id. | |
| # | |
| # If no token/repo is configured (e.g. running this file locally), | |
| # everything falls back to a local JSONL file under ./data so the | |
| # app still works end-to-end for development. | |
| # | |
| # Concurrency: adding a registration is a read-modify-write of one | |
| # file, so it is protected twice. A process-wide lock serializes | |
| # writers inside this Space, and every remote commit is pinned to | |
| # the revision it was computed from (parent_commit). If anything | |
| # else changed the dataset in the meantime — e.g. you editing a | |
| # status on the Hub while someone registers — the Hub rejects the | |
| # commit (HTTP 412) and the write is redone against the new state, | |
| # so no registration or manual edit is silently overwritten. | |
| # ===================================================================== | |
| HF_DATASET_REPO = os.environ.get("HF_DATASET_REPO", "SupraLabs/model-cup-registrations") | |
| HF_TOKEN = os.environ.get("HF_TOKEN") # set as a Space secret, never hardcoded | |
| REGISTRATIONS_FILENAME = "registrations.jsonl" | |
| MAX_WRITE_ATTEMPTS = 6 | |
| _LOCAL_FALLBACK_PATH = DATA_DIR / REGISTRATIONS_FILENAME | |
| _WRITE_LOCK = threading.Lock() | |
| try: | |
| from huggingface_hub import HfApi, hf_hub_download | |
| try: | |
| from huggingface_hub.errors import EntryNotFoundError, HfHubHTTPError, RepositoryNotFoundError | |
| except ImportError: # older huggingface_hub releases | |
| from huggingface_hub.utils import EntryNotFoundError, HfHubHTTPError, RepositoryNotFoundError | |
| _HUB_AVAILABLE = True | |
| except ImportError: | |
| _HUB_AVAILABLE = False | |
| _hub_api = HfApi(token=HF_TOKEN) if (_HUB_AVAILABLE and HF_TOKEN) else None | |
| class DuplicateRegistrationError(Exception): | |
| """The same Hugging Face repository is already registered.""" | |
| def __init__(self, existing: dict): | |
| super().__init__(existing.get("hf_repo", "")) | |
| self.existing = existing | |
| class RegistrationStorageError(Exception): | |
| """The registrations file couldn't be read or written safely.""" | |
| def _using_remote_dataset() -> bool: | |
| """True when this Space is properly configured to persist to a | |
| real HF Dataset. False means we're running against the local | |
| JSONL fallback (e.g. local dev, or misconfigured secrets).""" | |
| return _hub_api is not None | |
| def _parse_jsonl(text: str, strict: bool) -> list[dict]: | |
| """Parses a JSONL string. For display (strict=False) a malformed line | |
| — say, from a manual edit — is skipped so the page keeps working. | |
| For writes (strict=True) it aborts instead, because rewriting the | |
| file without that line would silently delete it.""" | |
| records = [] | |
| for number, line in enumerate(text.splitlines(), start=1): | |
| if not line.strip(): | |
| continue | |
| try: | |
| records.append(json.loads(line)) | |
| except json.JSONDecodeError: | |
| if strict: | |
| raise RegistrationStorageError( | |
| f"Line {number} of {REGISTRATIONS_FILENAME} is not valid JSON; refusing to overwrite the file." | |
| ) from None | |
| logger.warning("Skipping malformed line %d of %s", number, REGISTRATIONS_FILENAME) | |
| return records | |
| def _serialize_jsonl(records: list[dict]) -> str: | |
| return "".join(json.dumps(r, ensure_ascii=False) + "\n" for r in records) | |
| def _find_duplicate(records: list[dict], hf_repo: str) -> dict | None: | |
| """Same repo (case-insensitive) already registered. A Rejected entry | |
| doesn't block a new attempt.""" | |
| key = hf_repo.strip().lower() | |
| for r in records: | |
| if str(r.get("hf_repo", "")).strip().lower() == key and r.get("status") != "Rejected": | |
| return r | |
| return None | |
| def _download_remote_records(revision: str | None = None, strict: bool = False) -> list[dict]: | |
| """Reads the registrations file from the Dataset repo at a given | |
| revision (None = latest). A missing file just means no registrations yet.""" | |
| try: | |
| local_copy = hf_hub_download( | |
| repo_id=HF_DATASET_REPO, | |
| filename=REGISTRATIONS_FILENAME, | |
| repo_type="dataset", | |
| token=HF_TOKEN, | |
| revision=revision, | |
| ) | |
| except EntryNotFoundError: | |
| return [] | |
| return _parse_jsonl(Path(local_copy).read_text(encoding="utf-8"), strict=strict) | |
| def _read_registrations_jsonl() -> list[dict]: | |
| """Reads every registration record for display, remote Dataset if | |
| configured, otherwise the local fallback file.""" | |
| if _using_remote_dataset(): | |
| try: | |
| return _download_remote_records() | |
| except RepositoryNotFoundError: | |
| logger.warning("Dataset repo %s not found; showing no registrations.", HF_DATASET_REPO) | |
| return [] | |
| if not _LOCAL_FALLBACK_PATH.exists(): | |
| return [] | |
| return _parse_jsonl(_LOCAL_FALLBACK_PATH.read_text(encoding="utf-8"), strict=False) | |
| def _is_commit_conflict(exc: Exception) -> bool: | |
| """The Hub answers 412 (Precondition Failed) when parent_commit is | |
| no longer the head of the repo; 409 is treated the same way.""" | |
| status = getattr(getattr(exc, "response", None), "status_code", None) | |
| return status in (409, 412) | |
| def _append_remote(record: dict) -> None: | |
| last_error: Exception | None = None | |
| for attempt in range(MAX_WRITE_ATTEMPTS): | |
| # Pin everything to one revision: read it, change it, commit on top of it. | |
| head = _hub_api.repo_info(repo_id=HF_DATASET_REPO, repo_type="dataset").sha | |
| records = _download_remote_records(revision=head, strict=True) | |
| existing = _find_duplicate(records, record["hf_repo"]) | |
| if existing: | |
| raise DuplicateRegistrationError(existing) | |
| records.append(record) | |
| try: | |
| _hub_api.upload_file( | |
| path_or_fileobj=_serialize_jsonl(records).encode("utf-8"), | |
| path_in_repo=REGISTRATIONS_FILENAME, | |
| repo_id=HF_DATASET_REPO, | |
| repo_type="dataset", | |
| token=HF_TOKEN, | |
| commit_message=f"Register {record['hf_repo']}", | |
| parent_commit=head, | |
| ) | |
| return | |
| except HfHubHTTPError as exc: | |
| if not _is_commit_conflict(exc): | |
| raise | |
| last_error = exc | |
| logger.info("Registrations changed during write (attempt %d); retrying.", attempt + 1) | |
| time.sleep(random.uniform(0.2, 0.6) * (attempt + 1)) | |
| raise RegistrationStorageError("Too many concurrent changes to the registrations file.") from last_error | |
| def _append_local(record: dict) -> None: | |
| text = _LOCAL_FALLBACK_PATH.read_text(encoding="utf-8") if _LOCAL_FALLBACK_PATH.exists() else "" | |
| records = _parse_jsonl(text, strict=True) | |
| existing = _find_duplicate(records, record["hf_repo"]) | |
| if existing: | |
| raise DuplicateRegistrationError(existing) | |
| records.append(record) | |
| _LOCAL_FALLBACK_PATH.write_text(_serialize_jsonl(records), encoding="utf-8") | |
| def get_registered_models() -> list[dict]: | |
| """Returns every registered model. Always reads fresh from the | |
| Dataset (no in-memory cache) so multiple Space users/sessions see | |
| each other's registrations.""" | |
| return _read_registrations_jsonl() | |
| def add_registered_model(record: dict) -> None: | |
| """Atomically appends one new registration. | |
| Raises DuplicateRegistrationError if the repo is already registered, | |
| or RegistrationStorageError if it couldn't be saved safely.""" | |
| with _WRITE_LOCK: | |
| if _using_remote_dataset(): | |
| _append_remote(record) | |
| else: | |
| _append_local(record) | |
| # ===================================================================== | |
| # 4. MODELS — typed registration record shape | |
| # ===================================================================== | |
| class RegistrationRecord: | |
| organization: str | |
| organization_website: str | |
| organization_email: str | |
| contact_person: str | |
| model: str | |
| hf_repo: str | |
| license: str | |
| parameters: int | |
| category: str | |
| model_type: str | |
| provider: str | |
| endpoint: str | |
| api_docs_url: str | |
| supports_chat: bool | |
| supports_streaming: bool | |
| languages: str | |
| tags: str | |
| short_description: str | |
| long_description: str | |
| endpoint_api_key: str = "" # private: never rendered publicly, masked in to_public_dict() | |
| status: str = "Pending Review" | |
| def to_dict(self) -> dict: | |
| return asdict(self) | |
| def to_public_dict(self) -> dict: | |
| """Same as to_dict() but with the API key masked — used for | |
| anything shown back to the submitter.""" | |
| data = self.to_dict() | |
| if data["endpoint_api_key"]: | |
| data["endpoint_api_key"] = "***" | |
| return data | |
| # ===================================================================== | |
| # 5. COMPONENTS — reusable HTML fragment builders | |
| # Everything that comes from a registration is user-supplied and the | |
| # list page is public, so it is always passed through escape() — | |
| # otherwise a model named "<script>..." would run in every visitor's | |
| # browser. | |
| # ===================================================================== | |
| # Logo oficial da SupraLabs (linha branca, fundo transparente), embutido em | |
| # base64 para o app continuar sendo um arquivo único. Para trocar, gere outro | |
| # PNG branco/transparente e substitua o valor abaixo. | |
| LOGO_DATA_URI = "data:image/png;base64,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" | |
| def brand_header_html() -> str: | |
| """Faixa de marca no topo, com o logo oficial sobre o azul da SupraLabs.""" | |
| return f""" | |
| <div class="slc-brand"> | |
| <img class="slc-brand-mark" src="{LOGO_DATA_URI}" alt="SupraLabs" width="52" height="52"> | |
| <div class="slc-brand-text"> | |
| <span class="slc-brand-name">SupraLabs</span> | |
| <span class="slc-brand-sub">Open Source Model Cup</span> | |
| </div> | |
| </div>""" | |
| def page_head_html(title: str, lead: str, eyebrow: str = "") -> str: | |
| eyebrow_html = f'<div class="slc-eyebrow">{eyebrow}</div>' if eyebrow else "" | |
| return f""" | |
| <header class="slc-head"> | |
| {eyebrow_html} | |
| <h1>{title}</h1> | |
| <p>{lead}</p> | |
| </header>""" | |
| def status_label_html(status: str) -> str: | |
| color = STATUS_COLORS.get(status, "#7a828c") | |
| return f'<span class="slc-status" style="color:{color};">{escape(str(status))}</span>' | |
| def _format_parameters(value) -> str: | |
| try: | |
| return f"{int(value):,}" | |
| except (TypeError, ValueError): | |
| return escape(str(value)) | |
| def model_row_html(m: dict) -> str: | |
| def e(key: str, default: str = "—") -> str: | |
| return escape(str(m.get(key, default))) | |
| desc = e("short_description", "") | |
| desc_html = f'<div class="desc">{desc}</div>' if desc else "" | |
| return f""" | |
| <tr> | |
| <td> | |
| <div class="name">{e('model')}</div> | |
| <div class="org">{e('organization')}</div> | |
| {desc_html} | |
| </td> | |
| <td>{e('category')}</td> | |
| <td class="num">{_format_parameters(m.get('parameters'))}</td> | |
| <td>{e('model_type')}</td> | |
| <td>{e('provider')}</td> | |
| <td>{status_label_html(m.get('status', 'Pending Review'))}</td> | |
| </tr>""" | |
| def rule_section_html(title: str, items: list[str]) -> str: | |
| lis = "".join(f"<li>{item}</li>" for item in items) | |
| return f""" | |
| <details class="slc-accordion"> | |
| <summary>{title}</summary> | |
| <ul>{lis}</ul> | |
| </details>""" | |
| def faq_item_html(question: str, answer: str) -> str: | |
| return f""" | |
| <details class="slc-accordion"> | |
| <summary>{question}</summary> | |
| <p>{answer}</p> | |
| </details>""" | |
| def index_item_html(title: str, description: str) -> str: | |
| return f""" | |
| <li> | |
| <h3>{title}</h3> | |
| <p>{description}</p> | |
| </li>""" | |
| # ===================================================================== | |
| # 6. FORMS — registration submit handler | |
| # ===================================================================== | |
| def submit_registration( | |
| organization_name, organization_website, organization_email, contact_person, | |
| model_name, model_hf_url, license_, parameters_raw, model_type, | |
| inference_provider, inference_endpoint, api_docs_url, endpoint_api_key, | |
| supports_chat, supports_streaming, languages, tags, | |
| short_description, long_description, | |
| ): | |
| """ | |
| Validates the submitted form and, if valid, creates a new | |
| registration record with status 'Pending Review'. | |
| Returns (result_markdown, registration_json_text). | |
| """ | |
| fields = { | |
| "organization_name": organization_name, | |
| "organization_website": organization_website, | |
| "organization_email": organization_email, | |
| "contact_person": contact_person, | |
| "model_name": model_name, | |
| "model_hf_url": model_hf_url, | |
| "license": license_, | |
| "parameters_raw": parameters_raw, | |
| "model_type": model_type, | |
| "inference_provider": inference_provider, | |
| "inference_endpoint": inference_endpoint, | |
| "api_docs_url": api_docs_url, | |
| "endpoint_api_key": endpoint_api_key, | |
| "languages": languages, | |
| "tags": tags, | |
| "short_description": short_description, | |
| "long_description": long_description, | |
| } | |
| errors = validate_registration(fields) | |
| if errors: | |
| error_list = "\n".join(f"- {e}" for e in errors) | |
| return f"**Registration failed.** Fix the following and resubmit:\n\n{error_list}", "" | |
| parameters = parse_parameters(parameters_raw) | |
| category = categorize_by_parameters(parameters) | |
| record = RegistrationRecord( | |
| organization=organization_name.strip(), | |
| organization_website=organization_website.strip(), | |
| organization_email=organization_email.strip(), | |
| contact_person=contact_person.strip(), | |
| model=model_name.strip(), | |
| hf_repo=normalize_hf_repo(model_hf_url), | |
| license=license_.strip(), | |
| parameters=parameters, | |
| category=category, | |
| model_type=model_type, | |
| provider=inference_provider, | |
| endpoint=inference_endpoint.strip(), | |
| api_docs_url=(api_docs_url or "").strip(), | |
| supports_chat=(supports_chat == "Yes"), | |
| supports_streaming=(supports_streaming == "Yes"), | |
| languages=(languages or "").strip(), | |
| tags=(tags or "").strip(), | |
| short_description=short_description.strip(), | |
| long_description=(long_description or "").strip(), | |
| endpoint_api_key=(endpoint_api_key or "").strip(), | |
| status="Pending Review", | |
| ) | |
| try: | |
| add_registered_model(record.to_dict()) | |
| except DuplicateRegistrationError as exc: | |
| status = escape(str(exc.existing.get("status", "Unknown"))) | |
| return ( | |
| f"**Already registered.** `{record.hf_repo}` is already registered " | |
| f"(status: **{status}**). If you need to change something, contact the organizers.", | |
| "", | |
| ) | |
| except Exception: | |
| logger.exception("Could not save registration for %s", record.hf_repo) | |
| return ( | |
| "**Could not save your registration.** " | |
| "Something went wrong on our side. Please try again in a moment.", | |
| "", | |
| ) | |
| registration_json = json.dumps(record.to_public_dict(), indent=2, ensure_ascii=False) | |
| result_md = ( | |
| f"**Registration submitted.** {record.model} by {record.organization} was assigned to the " | |
| f"{record.category} category and is now Pending Review.\n\n" | |
| f"The organizers will review it, including the reported parameter count, before approving it. " | |
| f"The generated `registration.json` is shown below." | |
| ) | |
| return result_md, registration_json | |
| # ===================================================================== | |
| # 7. PAGES — one render function per screen | |
| # ===================================================================== | |
| def render_home_html() -> str: | |
| pages = [ | |
| ("Rules", "Everything you need to know before registering."), | |
| ("Tournament Structure", f"How the {_NUMBER_WORDS[len(TOURNAMENT_STRUCTURE)]} cups work."), | |
| ("Registered Models", "Every model registered so far, with review status."), | |
| ("FAQ", "Answers to the most common questions."), | |
| ("Roadmap", "What is planned for future seasons."), | |
| ] | |
| items = "".join(index_item_html(t, d) for t, d in pages) | |
| return f""" | |
| {page_head_html( | |
| "SupraLabs Open Source Model Cup", | |
| "Registration portal for the open-source model tournament. Read the rules, then register your model below.", | |
| "Registration portal", | |
| )} | |
| <ol class="slc-index">{items}</ol>""" | |
| def render_rules_html() -> str: | |
| sections = "".join(rule_section_html(title, items) for title, items in RULES_SECTIONS) | |
| return f""" | |
| {page_head_html("Tournament Rules", "Read every section carefully before registering a model.")} | |
| {sections}""" | |
| def render_tournament_structure_html() -> str: | |
| rows = "".join( | |
| f"<tr><td class='name'>{title}</td><td>{subtitle}</td></tr>" | |
| for title, subtitle in TOURNAMENT_STRUCTURE | |
| ) | |
| weight_class_names = _join_names([f"<strong>{n}</strong>" for n in WEIGHT_CLASS_NAMES], "and") | |
| classes_count = _NUMBER_WORDS[len(WEIGHT_CLASSES)] | |
| cups_count = _NUMBER_WORDS[len(TOURNAMENT_STRUCTURE)].capitalize() | |
| return f""" | |
| {page_head_html( | |
| f"{cups_count} cups, one tournament", | |
| "Every eligible model competes in its own weight class and in the Total World Cup.", | |
| )} | |
| <table class="slc-table"><tbody>{rows}</tbody></table> | |
| <p class="slc-note"> | |
| Models are assigned to one of {classes_count} weight classes — {weight_class_names} — automatically based on | |
| parameter count at registration time. Every model, regardless of weight class, also competes in the | |
| <strong>Total World Cup</strong> against every other registered model. | |
| </p>""" | |
| def render_registered_models_html(status_filter: str = "All") -> str: | |
| models = get_registered_models() | |
| if status_filter != "All": | |
| models = [m for m in models if m.get("status") == status_filter] | |
| if not models: | |
| body = "<p class='slc-note'>No models match this filter yet.</p>" | |
| else: | |
| rows = "".join(model_row_html(m) for m in models) | |
| body = f""" | |
| <table class="slc-table models"> | |
| <thead><tr> | |
| <th>Model</th><th>Category</th><th class="num">Parameters</th> | |
| <th>Type</th><th>Provider</th><th>Status</th> | |
| </tr></thead> | |
| <tbody>{rows}</tbody> | |
| </table>""" | |
| return f""" | |
| {page_head_html("Registered Models", f"{len(models)} model(s) shown.")} | |
| {body}""" | |
| def render_faq_html() -> str: | |
| items = "".join(faq_item_html(q, a) for q, a in FAQ_ITEMS) | |
| return f""" | |
| {page_head_html("Frequently Asked Questions", "Still have questions after this? Reach out to the organizers.")} | |
| {items}""" | |
| def render_roadmap_html() -> str: | |
| items = "".join(index_item_html(t, d) for t, d in ROADMAP_ITEMS) | |
| return f""" | |
| {page_head_html("Roadmap", "Features planned for future seasons of the tournament platform.")} | |
| <ol class="slc-index plain">{items}</ol>""" | |
| # ===================================================================== | |
| # 8. APP — Gradio wiring / navigation | |
| # Identidade visual SupraLabs: o azul-índigo do logo é a única cor | |
| # de destaque; o resto do texto/bordas vem das variáveis do tema do | |
| # Gradio, então claro/escuro continuam funcionando. A faixa de marca | |
| # no topo é o único elemento "pesado" — todo o resto é quieto. | |
| # ===================================================================== | |
| CUSTOM_CSS = """ | |
| :root { | |
| --slc-accent: #4558e8; | |
| --slc-accent-strong: #3745c9; | |
| --slc-accent-soft: #eef0fd; | |
| --slc-rule: var(--border-color-primary); | |
| } | |
| .dark { | |
| --slc-accent: #97a3ff; | |
| --slc-accent-strong: #b7c0ff; | |
| --slc-accent-soft: rgba(151, 163, 255, .10); | |
| } | |
| .gradio-container { max-width: 1040px !important; margin-left: auto !important; margin-right: auto !important; } | |
| /* ---------- Faixa de marca ---------- */ | |
| .slc-brand { | |
| display: flex; align-items: center; gap: 16px; | |
| padding: 16px 22px; border-radius: 10px; color: #fff; | |
| background: linear-gradient(135deg, #3b4fe0 0%, #5b6ef2 100%); | |
| } | |
| .slc-brand-mark { width: 52px; height: 52px; flex: none; } | |
| .slc-brand-text { display: flex; flex-direction: column; line-height: 1.15; } | |
| .slc-brand-name { font-size: 1.35rem; font-weight: 700; letter-spacing: -.01em; } | |
| .slc-brand-sub { font-size: .9rem; opacity: .82; margin-top: 2px; } | |
| /* ---------- Navegação ---------- */ | |
| .slc-nav { gap: 4px !important; margin: 6px 0 4px; flex-wrap: wrap; border-bottom: 1px solid var(--slc-rule); padding-bottom: 0; } | |
| button.slc-tab { | |
| background: transparent !important; border: none !important; box-shadow: none !important; | |
| border-bottom: 2px solid transparent !important; border-radius: 0 !important; | |
| color: var(--body-text-color) !important; font-weight: 600 !important; | |
| padding: 8px 12px !important; transition: color .15s, border-color .15s; | |
| } | |
| button.slc-tab:hover { color: var(--slc-accent) !important; border-bottom-color: var(--slc-accent) !important; } | |
| button.slc-tab:focus-visible { outline: 2px solid var(--slc-accent); outline-offset: 2px; } | |
| /* ---------- Cabeçalho de página ---------- */ | |
| .slc-head { padding: 14px 0 18px; margin-bottom: 14px; border-bottom: 1px solid var(--slc-rule); } | |
| .slc-eyebrow { font-size: .85rem; color: var(--slc-accent); margin-bottom: 4px; font-weight: 600; } | |
| .slc-head h1 { font-size: 1.8rem; font-weight: 700; margin: 0 0 6px; line-height: 1.2; letter-spacing: -.015em; color: var(--body-text-color); } | |
| .slc-head p { margin: 0; color: var(--body-text-color-subdued); max-width: 62ch; line-height: 1.55; } | |
| /* ---------- Índice (home / roadmap): anel do logo como marcador ---------- */ | |
| .slc-index { list-style: none; margin: 0; padding: 0; } | |
| .slc-index li { | |
| position: relative; padding: 12px 0 12px 30px; border-bottom: 1px solid var(--slc-rule); | |
| } | |
| .slc-index li::before { | |
| content: ""; position: absolute; left: 4px; top: 17px; width: 11px; height: 11px; | |
| border: 2px solid var(--slc-accent); border-radius: 50%; | |
| } | |
| .slc-index li h3 { margin: 0; font-size: 1rem; font-weight: 650; color: var(--body-text-color); } | |
| .slc-index li p { margin: 2px 0 0; color: var(--body-text-color-subdued); font-size: .92rem; } | |
| /* ---------- Nota ---------- */ | |
| .slc-note { | |
| color: var(--body-text-color-subdued); font-size: .92rem; line-height: 1.65; max-width: 70ch; | |
| margin-top: 18px; padding-left: 14px; border-left: 3px solid var(--slc-accent); | |
| } | |
| /* ---------- Acordeões (regras / FAQ) ---------- */ | |
| .slc-accordion { border-bottom: 1px solid var(--slc-rule); padding: 12px 0; color: var(--body-text-color); } | |
| .slc-accordion summary { | |
| cursor: pointer; font-weight: 600; font-size: 1rem; list-style: none; | |
| display: flex; justify-content: space-between; align-items: center; gap: 16px; | |
| } | |
| .slc-accordion summary::-webkit-details-marker { display: none; } | |
| .slc-accordion summary::after { content: "+"; font-size: 1.3rem; font-weight: 400; color: var(--slc-accent); line-height: 1; } | |
| .slc-accordion[open] summary::after { content: "\\2212"; } | |
| .slc-accordion summary:hover { color: var(--slc-accent); } | |
| .slc-accordion summary:focus-visible { outline: 2px solid var(--slc-accent); outline-offset: 4px; border-radius: 2px; } | |
| .slc-accordion ul { margin: 10px 0 2px; padding-left: 20px; font-size: .93rem; line-height: 1.7; } | |
| .slc-accordion ul li::marker { color: var(--slc-accent); } | |
| .slc-accordion p { margin: 10px 0 2px; font-size: .93rem; line-height: 1.7; max-width: 70ch; } | |
| /* ---------- Tabelas ---------- */ | |
| table.slc-table { width: 100%; border-collapse: collapse; font-size: .92rem; } | |
| table.slc-table th { | |
| text-align: left; font-weight: 600; font-size: .85rem; color: var(--body-text-color-subdued); | |
| padding: 8px 12px 8px 0; border-bottom: 2px solid var(--slc-accent); | |
| } | |
| table.slc-table td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--slc-rule); vertical-align: top; color: var(--body-text-color); } | |
| table.slc-table .num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 20px; } | |
| table.slc-table .name { font-weight: 650; } | |
| table.slc-table .org { color: var(--body-text-color-subdued); font-size: .82rem; } | |
| table.slc-table .desc { color: var(--body-text-color-subdued); font-size: .84rem; margin-top: 4px; max-width: 46ch; line-height: 1.45; } | |
| /* ---------- Status: ponto + texto na cor do estado ---------- */ | |
| .slc-status { font-weight: 600; font-size: .88rem; white-space: nowrap; display: inline-flex; align-items: center; gap: 6px; } | |
| .slc-status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; } | |
| /* ---------- Formulário ---------- */ | |
| .slc-register h2 { margin: 6px 0 2px; font-size: 1.5rem; font-weight: 700; letter-spacing: -.01em; } | |
| .slc-form-title h4 { | |
| margin: 0 0 4px; padding-bottom: 6px; font-size: 1.02rem; font-weight: 700; | |
| color: var(--slc-accent); border-bottom: 2px solid var(--slc-accent); | |
| } | |
| @media (max-width: 640px) { | |
| .slc-brand { padding: 14px 16px; } | |
| .slc-brand-mark { width: 44px; height: 44px; } | |
| .slc-head h1 { font-size: 1.5rem; } | |
| table.slc-table.models { display: block; overflow-x: auto; } | |
| } | |
| """ | |
| STATUS_FILTER_CHOICES = ["All", "Verified", "Pending Review", "Rejected", "Offline"] | |
| def go_home(): | |
| return ( | |
| gr.update(visible=True, value=render_home_html()), | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=False), gr.update(visible=False), | |
| ) | |
| def go_rules(): | |
| return ( | |
| gr.update(visible=False), | |
| gr.update(visible=True, value=render_rules_html()), | |
| gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=False), gr.update(visible=False), | |
| ) | |
| def go_structure(): | |
| return ( | |
| gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=True, value=render_tournament_structure_html()), | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), | |
| ) | |
| def go_registered(status_filter="All"): | |
| return ( | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=True, value=render_registered_models_html(status_filter)), | |
| gr.update(visible=False), gr.update(visible=False), | |
| ) | |
| def go_faq(): | |
| return ( | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=True, value=render_faq_html()), | |
| gr.update(visible=False), | |
| ) | |
| def go_roadmap(): | |
| return ( | |
| gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=False), gr.update(visible=False), | |
| gr.update(visible=True, value=render_roadmap_html()), | |
| ) | |
| _BRAND_BLUE = gr.themes.Color( | |
| c50="#eff1fe", c100="#e0e4fd", c200="#c4cbfb", c300="#a1abf8", c400="#7d8af3", | |
| c500="#5f6eed", c600="#4558e8", c700="#3745c9", c800="#2e3aa3", c900="#29347f", c950="#1b2150", | |
| ) | |
| THEME = gr.themes.Base( | |
| primary_hue=_BRAND_BLUE, | |
| neutral_hue="slate", | |
| radius_size=gr.themes.sizes.radius_sm, | |
| font=[gr.themes.GoogleFont("Manrope"), gr.themes.Font("ui-sans-serif"), gr.themes.Font("system-ui"), gr.themes.Font("sans-serif")], | |
| font_mono=[gr.themes.Font("ui-monospace"), gr.themes.Font("SFMono-Regular"), gr.themes.Font("monospace")], | |
| ).set( | |
| button_primary_background_fill="*primary_600", | |
| button_primary_background_fill_hover="*primary_700", | |
| button_primary_background_fill_dark="*primary_500", | |
| button_primary_background_fill_hover_dark="*primary_400", | |
| button_primary_text_color="white", | |
| button_primary_text_color_dark="white", | |
| input_border_color_focus="*primary_500", | |
| input_border_color_focus_dark="*primary_400", | |
| ) | |
| with gr.Blocks(title="SupraLabs Open Source Model Cup — Registration Portal") as demo: | |
| gr.HTML(brand_header_html()) | |
| with gr.Row(elem_classes="slc-nav"): | |
| home_btn = gr.Button("Home", size="sm", elem_classes="slc-tab") | |
| rules_btn = gr.Button("Rules", size="sm", elem_classes="slc-tab") | |
| structure_btn = gr.Button("Tournament Structure", size="sm", elem_classes="slc-tab") | |
| registered_btn = gr.Button("Registered Models", size="sm", elem_classes="slc-tab") | |
| faq_btn = gr.Button("FAQ", size="sm", elem_classes="slc-tab") | |
| roadmap_btn = gr.Button("Roadmap", size="sm", elem_classes="slc-tab") | |
| home_html = gr.HTML(render_home_html(), visible=True) | |
| rules_html = gr.HTML(visible=False) | |
| structure_html = gr.HTML(visible=False) | |
| registered_html = gr.HTML(visible=False) | |
| faq_html = gr.HTML(visible=False) | |
| roadmap_html = gr.HTML(visible=False) | |
| info_screens = [home_html, rules_html, structure_html, registered_html, faq_html, roadmap_html] | |
| status_filter_dropdown = gr.Dropdown( | |
| choices=STATUS_FILTER_CHOICES, value="All", label="Filter registered models by status", visible=True | |
| ) | |
| home_btn.click(go_home, outputs=info_screens) | |
| rules_btn.click(go_rules, outputs=info_screens) | |
| structure_btn.click(go_structure, outputs=info_screens) | |
| registered_btn.click(go_registered, inputs=status_filter_dropdown, outputs=info_screens) | |
| status_filter_dropdown.change(go_registered, inputs=status_filter_dropdown, outputs=info_screens) | |
| faq_btn.click(go_faq, outputs=info_screens) | |
| roadmap_btn.click(go_roadmap, outputs=info_screens) | |
| gr.Markdown("---") | |
| gr.Markdown("## Register a model", elem_classes="slc-register") | |
| gr.Markdown("Fill in every field below. Fields marked with * are required.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### Organization", elem_classes="slc-form-title") | |
| organization_name = gr.Textbox(label="Organization Name *") | |
| organization_website = gr.Textbox(label="Organization Website *", placeholder="https://example.com") | |
| organization_email = gr.Textbox(label="Organization Email *", placeholder="team@example.com") | |
| contact_person = gr.Textbox(label="Contact Person *") | |
| with gr.Column(): | |
| gr.Markdown("#### Model", elem_classes="slc-form-title") | |
| model_name = gr.Textbox(label="Model Name *") | |
| model_hf_url = gr.Textbox(label="Model Hugging Face URL *", placeholder="https://huggingface.co/org/model") | |
| license_box = gr.Textbox(label="License *", placeholder="apache-2.0, mit, ...") | |
| parameters_raw = gr.Textbox( | |
| label="Number of Parameters *", | |
| placeholder="67000000 or 67M or 1.3B", | |
| info=( | |
| f"Maximum {MAX_PARAMETERS_LABEL}. " if MAX_PARAMETERS_LABEL else "" | |
| ) + "The organizers verify this number before approving your model.", | |
| ) | |
| model_type = gr.Dropdown(choices=MODEL_TYPES, label="Model Type *") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### Inference", elem_classes="slc-form-title") | |
| inference_provider = gr.Dropdown(choices=PROVIDERS, label="Inference Provider *") | |
| inference_endpoint = gr.Textbox(label="Inference Endpoint *", placeholder="https://...") | |
| endpoint_api_key = gr.Textbox( | |
| label="Endpoint API Key", | |
| type="password", | |
| placeholder="optional", | |
| info=( | |
| "Only if your endpoint requires authentication. Use a dedicated key you can revoke, " | |
| "ideally with a spending limit. It is stored privately and never shown publicly." | |
| ), | |
| ) | |
| api_docs_url = gr.Textbox(label="API Documentation URL", placeholder="https://... (optional)") | |
| supports_chat = gr.Radio(choices=YES_NO, label="Supports Chat?", value="No") | |
| supports_streaming = gr.Radio(choices=YES_NO, label="Supports Streaming?", value="No") | |
| with gr.Column(): | |
| gr.Markdown("#### Description", elem_classes="slc-form-title") | |
| languages = gr.Textbox(label="Languages", placeholder="English, Spanish, ...") | |
| tags = gr.Textbox(label="Tags", placeholder="chat, instruct, small, ...") | |
| short_description = gr.Textbox(label="Short Description *", placeholder="One sentence about the model.") | |
| long_description = gr.Textbox(label="Long Description", lines=4, placeholder="Optional — more detail about the model.") | |
| submit_btn = gr.Button("Submit registration", variant="primary") | |
| submission_result = gr.Markdown() | |
| registration_json_output = gr.Code(label="Generated registration.json", language="json") | |
| submit_btn.click( | |
| submit_registration, | |
| inputs=[ | |
| organization_name, organization_website, organization_email, contact_person, | |
| model_name, model_hf_url, license_box, parameters_raw, model_type, | |
| inference_provider, inference_endpoint, api_docs_url, endpoint_api_key, | |
| supports_chat, supports_streaming, languages, tags, | |
| short_description, long_description, | |
| ], | |
| outputs=[submission_result, registration_json_output], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=THEME, css=CUSTOM_CSS) |