Spaces:
Running
Running
:lipstick: Improve refresh button and control layout
Browse files- backend/api.py +11 -1
- backend/rate_limiter.py +99 -0
- config/settings.py +4 -1
- frontend/layout.py +23 -20
- submission_tracking.json +5 -0
backend/api.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import asyncio
|
| 2 |
from typing import Dict, Any
|
| 3 |
from .submission import process_submission
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
# Sample data - replace with actual data loading
|
|
@@ -23,8 +24,14 @@ async def submit_model(
|
|
| 23 |
pretraining: str = "",
|
| 24 |
organization: str = ""
|
| 25 |
) -> Dict[str, Any]:
|
| 26 |
-
"""API endpoint for model submission."""
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
record = await process_submission(
|
| 29 |
model_name=model_name,
|
| 30 |
hf_space_tag=hf_space_tag,
|
|
@@ -38,6 +45,9 @@ async def submit_model(
|
|
| 38 |
true_labels=SAMPLE_LABELS
|
| 39 |
)
|
| 40 |
|
|
|
|
|
|
|
|
|
|
| 41 |
return record
|
| 42 |
|
| 43 |
|
|
|
|
| 1 |
import asyncio
|
| 2 |
from typing import Dict, Any
|
| 3 |
from .submission import process_submission
|
| 4 |
+
from .rate_limiter import check_submission_limit, record_submission, get_submission_status
|
| 5 |
|
| 6 |
|
| 7 |
# Sample data - replace with actual data loading
|
|
|
|
| 24 |
pretraining: str = "",
|
| 25 |
organization: str = ""
|
| 26 |
) -> Dict[str, Any]:
|
| 27 |
+
"""API endpoint for model submission with rate limiting."""
|
| 28 |
|
| 29 |
+
# Check rate limit
|
| 30 |
+
if not check_submission_limit(hf_space_tag):
|
| 31 |
+
status = get_submission_status(hf_space_tag)
|
| 32 |
+
raise Exception(f"Daily submission limit exceeded. Used {status['submissions_today']}/{status['daily_limit']} submissions today. Try again tomorrow.")
|
| 33 |
+
|
| 34 |
+
# Process submission
|
| 35 |
record = await process_submission(
|
| 36 |
model_name=model_name,
|
| 37 |
hf_space_tag=hf_space_tag,
|
|
|
|
| 45 |
true_labels=SAMPLE_LABELS
|
| 46 |
)
|
| 47 |
|
| 48 |
+
# Record successful submission for rate limiting
|
| 49 |
+
record_submission(hf_space_tag)
|
| 50 |
+
|
| 51 |
return record
|
| 52 |
|
| 53 |
|
backend/rate_limiter.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from datetime import datetime, date
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
from config.settings import DAILY_SUBMISSION_LIMIT, DEBUG_MODE
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
RATE_LIMIT_FILE = "submission_tracking.json"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def load_submission_tracking() -> Dict[str, Any]:
|
| 12 |
+
"""Load submission tracking data from file."""
|
| 13 |
+
if not os.path.exists(RATE_LIMIT_FILE):
|
| 14 |
+
return {}
|
| 15 |
+
|
| 16 |
+
with open(RATE_LIMIT_FILE, 'r') as f:
|
| 17 |
+
return json.load(f)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def save_submission_tracking(data: Dict[str, Any]) -> None:
|
| 21 |
+
"""Save submission tracking data to file."""
|
| 22 |
+
with open(RATE_LIMIT_FILE, 'w') as f:
|
| 23 |
+
json.dump(data, f, indent=2)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def check_submission_limit(hf_space_tag: str) -> bool:
|
| 27 |
+
"""
|
| 28 |
+
Check if the HF space can submit (hasn't exceeded daily limit).
|
| 29 |
+
|
| 30 |
+
Returns:
|
| 31 |
+
True if submission is allowed, False if rate limited
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
# Debug mode = unlimited submissions
|
| 35 |
+
if DEBUG_MODE:
|
| 36 |
+
return True
|
| 37 |
+
|
| 38 |
+
today = str(date.today())
|
| 39 |
+
data = load_submission_tracking()
|
| 40 |
+
|
| 41 |
+
# Initialize space tracking if not exists
|
| 42 |
+
if hf_space_tag not in data:
|
| 43 |
+
data[hf_space_tag] = {}
|
| 44 |
+
|
| 45 |
+
# Get today's submission count for this space
|
| 46 |
+
today_count = data[hf_space_tag].get(today, 0)
|
| 47 |
+
|
| 48 |
+
# Check if limit exceeded
|
| 49 |
+
return today_count < DAILY_SUBMISSION_LIMIT
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def record_submission(hf_space_tag: str) -> None:
|
| 53 |
+
"""Record a successful submission for rate limiting."""
|
| 54 |
+
|
| 55 |
+
today = str(date.today())
|
| 56 |
+
data = load_submission_tracking()
|
| 57 |
+
|
| 58 |
+
# Initialize space tracking if not exists
|
| 59 |
+
if hf_space_tag not in data:
|
| 60 |
+
data[hf_space_tag] = {}
|
| 61 |
+
|
| 62 |
+
# Increment today's count
|
| 63 |
+
data[hf_space_tag][today] = data[hf_space_tag].get(today, 0) + 1
|
| 64 |
+
|
| 65 |
+
# Clean up old dates (keep last 7 days for reference)
|
| 66 |
+
cleanup_old_entries(data)
|
| 67 |
+
|
| 68 |
+
save_submission_tracking(data)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def cleanup_old_entries(data: Dict[str, Any]) -> None:
|
| 72 |
+
"""Remove entries older than 7 days to keep file small."""
|
| 73 |
+
|
| 74 |
+
from datetime import timedelta
|
| 75 |
+
cutoff_date = str(date.today() - timedelta(days=7))
|
| 76 |
+
|
| 77 |
+
for space_tag in data:
|
| 78 |
+
old_dates = [d for d in data[space_tag] if d < cutoff_date]
|
| 79 |
+
for old_date in old_dates:
|
| 80 |
+
del data[space_tag][old_date]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def get_submission_status(hf_space_tag: str) -> Dict[str, Any]:
|
| 84 |
+
"""Get current submission status for a space."""
|
| 85 |
+
|
| 86 |
+
today = str(date.today())
|
| 87 |
+
data = load_submission_tracking()
|
| 88 |
+
|
| 89 |
+
today_count = data.get(hf_space_tag, {}).get(today, 0)
|
| 90 |
+
remaining = max(0, DAILY_SUBMISSION_LIMIT - today_count)
|
| 91 |
+
|
| 92 |
+
return {
|
| 93 |
+
"space": hf_space_tag,
|
| 94 |
+
"submissions_today": today_count,
|
| 95 |
+
"daily_limit": DAILY_SUBMISSION_LIMIT,
|
| 96 |
+
"remaining": remaining,
|
| 97 |
+
"debug_mode": DEBUG_MODE,
|
| 98 |
+
"can_submit": check_submission_limit(hf_space_tag)
|
| 99 |
+
}
|
config/settings.py
CHANGED
|
@@ -33,9 +33,12 @@ APP_DESCRIPTION = "Leaderboard for molecular toxicity prediction on the Tox21 da
|
|
| 33 |
|
| 34 |
# Evaluation Settings
|
| 35 |
DEFAULT_METRIC = "roc_auc"
|
| 36 |
-
SUBMISSION_RATE_LIMIT = 5 # Max submissions per day per user
|
| 37 |
EVALUATION_TIMEOUT = 3600 # 1 hour timeout for evaluations
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
# Display Settings
|
| 40 |
MAX_MODELS_DISPLAYED = 100
|
| 41 |
DEFAULT_SORT_BY = "average_score"
|
|
|
|
| 33 |
|
| 34 |
# Evaluation Settings
|
| 35 |
DEFAULT_METRIC = "roc_auc"
|
|
|
|
| 36 |
EVALUATION_TIMEOUT = 3600 # 1 hour timeout for evaluations
|
| 37 |
|
| 38 |
+
# Rate Limiting
|
| 39 |
+
DEBUG_MODE = os.environ.get("DEBUG_SUBMISSIONS", "false").lower() == "true"
|
| 40 |
+
DAILY_SUBMISSION_LIMIT = 999 if DEBUG_MODE else 5 # Per HF space per day
|
| 41 |
+
|
| 42 |
# Display Settings
|
| 43 |
MAX_MODELS_DISPLAYED = 100
|
| 44 |
DEFAULT_SORT_BY = "average_score"
|
frontend/layout.py
CHANGED
|
@@ -16,28 +16,31 @@ def create_leaderboard_tab(refresh_callback: Callable = None) -> gr.TabItem:
|
|
| 16 |
|
| 17 |
with gr.TabItem("🏅 Leaderboard", elem_id="leaderboard-tab", id=0) as tab:
|
| 18 |
|
| 19 |
-
# Header section
|
| 20 |
-
|
| 21 |
-
gr.HTML(LeaderboardContent.get_header_html())
|
| 22 |
-
refresh_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
|
| 23 |
|
| 24 |
-
#
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
search_columns=["Model", "Model Description", "Publication"], # or e.g. ["Model", "Model Description", "Publication"]
|
| 31 |
-
select_columns=[], # or e.g. ["Publication"]
|
| 32 |
-
filter_columns=[], # or a list of valid column names/filters
|
| 33 |
-
hide_columns=[], # keep explicit to be safe
|
| 34 |
-
elem_id="leaderboard-table",
|
| 35 |
-
height=480, # only controls vertical
|
| 36 |
-
min_width=160, # doesn’t prevent horizontal scroll
|
| 37 |
-
wrap=True,
|
| 38 |
-
column_widths=[200, 300, 150, 100, 120, 100] + [80] * 12,
|
| 39 |
-
)
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# Connect refresh button
|
| 43 |
def refresh_data():
|
|
|
|
| 16 |
|
| 17 |
with gr.TabItem("🏅 Leaderboard", elem_id="leaderboard-tab", id=0) as tab:
|
| 18 |
|
| 19 |
+
# Header section
|
| 20 |
+
header_html = gr.HTML(LeaderboardContent.get_header_html())
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
# Leaderboard with integrated refresh button
|
| 23 |
+
with gr.Row():
|
| 24 |
+
with gr.Column(scale=10):
|
| 25 |
+
# Load initial data
|
| 26 |
+
result_data = refresh_leaderboard().reset_index(drop=True)
|
| 27 |
+
result_data.columns = result_data.columns.map(str)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
leaderboard_table = Leaderboard(
|
| 30 |
+
value=result_data,
|
| 31 |
+
search_columns=["Model", "Model Description", "Publication"],
|
| 32 |
+
select_columns=[],
|
| 33 |
+
filter_columns=[],
|
| 34 |
+
hide_columns=[],
|
| 35 |
+
elem_id="leaderboard-table",
|
| 36 |
+
height=480,
|
| 37 |
+
min_width=160,
|
| 38 |
+
wrap=True,
|
| 39 |
+
column_widths=[200, 300, 150, 100, 120, 100] + [80] * 12,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
with gr.Column(scale=1, min_width=100):
|
| 43 |
+
refresh_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
|
| 44 |
|
| 45 |
# Connect refresh button
|
| 46 |
def refresh_data():
|
submission_tracking.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"test/space": {
|
| 3 |
+
"2025-08-30": 5
|
| 4 |
+
}
|
| 5 |
+
}
|