AxionLab-official's picture
Update app.py
fb0de33 verified
Raw History Blame Contribute Delete
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
@staticmethod
def GPU(func=None, **kwargs):
def wrapper(f):
return f
return wrapper(func) if func else wrapper
@spaces.GPU
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
# =====================================================================
@dataclass
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)